Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
27
Help us understand the problem. What is going on with this article?
@Yamamoto0525

TypeScriptとPHP8の言語比較をまとめてみた

はじめに

ここ数年TypeScriptを使うことが多かったのですが、今度案件でLaravel(PHP8)を使うことになりました。
いつもの調子で書いているとバグを生みそうだと感じたので、間違えそうな箇所を中心にそれぞれの言語の差異をまとめてみました。
wiki的な使い方ができればと思いますので、誤りや追加すべき事項がありましたらご指摘ください。

目次

データ型の比較

Bool型の判定

TypeScript
// 差異がない判定
console.log(!!(""));          // false
console.log(!!(0));           // false
console.log(!!(null));        // false
console.log(!!(1));           // true
console.log(!!(-2));          // true
console.log(!!("foo"));       // true
console.log(!!(2.3e5));       // true
console.log(!![12]);          // true
console.log(!!{});            // true
console.log(!!("false"));     // true
// 差異のある判定
console.log(!!([]));          // true
console.log(!!(NaN));         // false
console.log(!!("0"));         // true
// TypeScriptのみ
console.log(!!(undefined));   // false
PHP8
<?php
// 差異がない判定
var_dump((bool) "");            // bool(false)
var_dump((bool) 0);             // bool(false)
var_dump((bool) null);          // bool(false)
var_dump((bool) 1);             // bool(true)
var_dump((bool) -2);            // bool(true)
var_dump((bool) "foo");         // bool(true)
var_dump((bool) 2.3e5);         // bool(true)
var_dump((bool) [12]);          // bool(true)
var_dump((bool) (object)[]);    // bool(true)
var_dump((bool) "false");       // bool(true)
// 差異のある判定
var_dump((bool) []);            // bool(false)
var_dump((bool) NAN);           // bool(true)
var_dump((bool) "0");           // bool(false)

?>

数値型の判定

TypeScript
// 全て数値型
console.log(typeof 1);         // "number"
console.log(typeof 0);         // "number"
console.log(typeof -1);        // "number"
console.log(typeof 0.1);       // "number"
console.log(typeof 1.2e3);     // "number"
console.log(typeof 7E-10);     // "number"
console.log(typeof 1_234.567); // "number"
console.log(typeof NaN);       // "number"
PHP8
<?php
// 整数型
var_dump(1);          // int(1)
var_dump(0);          // int(0)
var_dump(-1);         // int(-1)
// 浮動小数点数型
var_dump(0.1);        // float(0.1)
var_dump(1.2e3);      // float(1200)
var_dump(7E-10);      // float(7.0E-10)
var_dump(1_234.567);  // float(1234.567)
var_dump(NAN);        // float(NAN)
?>

補足
  • PHPは8.0.0とその前とのバージョンで数値型の挙動に差異があります。
PHP8.0.0
<?php
var_dump(1 + "10.5");                // float(11.5)
var_dump(1 + "-1.3e3");              // float(-1299)
var_dump(1 + "bob-1.3e3");           // Uncaught TypeError
var_dump(1 + "bob3");                // Uncaught TypeError
var_dump(1 + "10 Small Pigs");       // int(11) Warning
var_dump(4 + "10.2 Little Piggies"); // float(14.2) Warning
var_dump("10.0 pigs " + 1);          // float(11) Warning
var_dump("10.0 pigs " + 1.0);        // float(11) Warning
?>
PHP5.6.40
<?php
var_dump(1 + "10.5");                // float(11.5)
var_dump(1 + "-1.3e3");              // float(-1299)
var_dump(1 + "bob-1.3e3");           // int(1)
var_dump(1 + "bob3");                // int(1)
var_dump(1 + "10 Small Pigs");       // int(11) 
var_dump(4 + "10.2 Little Piggies"); // float(14.2) 
var_dump("10.0 pigs " + 1);          // float(11) 
var_dump("10.0 pigs " + 1.0);        // float(11) 
?>

文字列の連結

TypeScript
let a = "ABC";
let b = a + "D";
console.log(b); // "ABCD"
PHP8
<?php
$a = "ABC";
$b = $a . "D";
echo $b; // ABCD
?>

ヒアドキュメント(Nowdoc)

TypeScript
let name = "hoge";
let foo = {foo: "fuga", bar: ["fuga"]};
const text = `
My name is ${name}. I am printing some ${foo.foo}.
Now, I am printing some ${foo.bar[0]}.
This should print a capital 'A': \x41
`;
console.log(text);
PHP8
<?php
$name = "hoge";
$foo = ["foo"=>"fuga", "bar"=>["fuga"]];
$text_1 = <<<EOD
My name is "$name". I am printing some $foo[foo].
Now, I am printing some {$foo["bar"][0]}.
This should print a capital 'A': \x41
EOD;
echo $text_1 . "\n\n";

// シングルクオーテーションでパースを回避
$text_2 = <<<'EOD'
My name is "$name". I am printing some $foo[foo].
Now, I am printing some {$foo["bar"][0]}.
This should print a capital 'A': \x41
EOD;
echo $text_2;
?>

配列の作成

TypeScript
let array_1 = [1, 2, 3];
let array_2 = new Array(1, 2, 3);
console.log(array_1); // [1, 2, 3]
console.log(array_2); // [1, 2, 3] 
PHP8
<?php
$array_1 = [1, 2, 3];
$array_2 = array(1, 2, 3);
print_r($array_1); // Array( [0] => 1 [1] => 2 [2] => 3)
print_r($array_2); // Array( [0] => 1 [1] => 2 [2] => 3)
?>

連想配列(オブジェクト)の作成

TypeScript
let obj = {a: "hoge", b: 1};
console.log(obj.a); // "hoge"
PHP8
<?php
$array = ["a" => "hoge", "b" => 1];
print_r($array["a"]); // hoge
?>

補足
  • TypeScriptとPHPでは連想配列のkeyを上書きする判定基準が異なります。
TypeScript
<?php
let obj = {
        1: "a", 
        "1": "b",
        1.5: "c",
        true: "d",
    };
console.log(obj); // {"1": "b", "1.5": "c", "true": "d"} 
?>
PHP8
<?php
$array = array(
    1    => "a",
    "1"  => "b",
    1.5  => "c",
    true => "d",
);
var_dump($array); // array(1) { [1] => string(1) "d"}
?>

型宣言の比較

基本的な型の比較

TypeScript
class MyClass {
}
const a = "hoge";
const b = 1;
const c = 0.1;
const d = {};
const e = new MyClass();
const f = ['fuga'];
const g = true;
const h = 2;
const i = null;

function test(
    string: string,         // 文字列
    int: number,            // 整数型
    float: number,          // 浮動小数点型
    object: Object,         // オブジェクト型  
    my_class: MyClass,      // クラス型  
    array: string[],        // 配列型  
    boolean: boolean,       // 論理型  
    union: string | number, // ユニオン型  
    any: any,               // 型チェックなし 
){
    console.log(string);   // "hoge" 
    console.log(int);      // 1
    console.log(float);    // 0.1
    console.log(object);   // {} 
    console.log(my_class); // MyClass: {} 
    console.log(array);    // ["fuga"] 
    console.log(boolean);  // true
    console.log(union);    // 2
    console.log(any);      // null
}

test(a, b, c, d, e, f, g, h, i);
PHP8
<?php
class MyClass {
}
$a = "hoge";
$b = 1;
$c = 0.1;
$d = new MyClass();
$e = new MyClass();
$f = ['fuga'];
$g = true;
$h = 2;
$i = null;

function test(
  string $string,      // 文字列
  int $int,            // 整数型
  float $float,        // 浮動小数点型
  object $object,      // オブジェクト型  
  MyClass $class,      // クラス型  
  array $array,        // 配列型  
  bool $bool,          // 論理型  
  string | int $union, // ユニオン型  
  mixed $mixed,        // 型チェックなし 
){
    var_dump($string) ; // string(4) "hoge"
    var_dump($int) ;    // int(1)
    var_dump($float) ;  // float(0.1)
    var_dump($object) ; // object(MyClass)#1 (0) {}
    var_dump($class) ;  // object(MyClass)#2 (0) {}
    var_dump($array) ;  // array(1) { [0]=>string(4) "fuga"}
    var_dump($bool) ;   // bool(true)
    var_dump($union) ;  // int(2)
    var_dump($mixed) ;  // NULL
}

test($a, $b, $c, $d, $e, $f, $g, $h, $i);
?>

補足
  • それぞれの言語独自の型は以下になります。
TypeScript
enum Color {Red, Green}
const a = ["hoge", 1];
const b = undefined;
const c = null;
const d = Color.Green;

function test(
    tuple: [string, number],  // タプル型
    my_undefined: undefined,  // Undefined型
    my_null: null,            // Null型
    my_enum: Color,           // 列挙型  
){
    console.log(tuple);         // ["hoge", 1] 
    console.log(my_undefined);  // undefined
    console.log(my_null);       // null
    console.log(my_enum);       // 1
}

test(a, b, c, d);

Null/Undefinedを許容する型宣言

TypeScript
class MyClass {}

function f(arg?: MyClass | null) {
    console.log(arg);
}

f(new MyClass); // MyClass {}
f(null);        // null
f();            // undefined
PHP8
<?php
class MyClass {}

function f(?MyClass $c) {
    var_dump($c);
}

f(new MyClass); // object(MyClass)#1 (0) {}
f(null);        // NULL
?>

関数の返り値

TypeScript、PHP8のどちらも同じように返り値を指定できますが、型の扱いについて若干の違いがあります。
TypeScriptでは型判定によるアラート表示のみで返り値を変更することはしませんが、PHP8では指定された返り値への型キャストが実行されます。

TypeScript
function func(num: number): number
{
    return num;
}
console.log(func(4)); //  4
console.log(func("4")); // "4" 
PHP8
<?php
function func(int $num): int
{
    return $num;
}
var_dump(func(4)); //  int(4)
var_dump(func("4")); // int(4)
?>

型の変換(アサーションとキャスト)

TypeScriptにおける型の変換(型アサーション)は純粋にコンパイル時の構造体であり、コードをどのように解析するかについてのヒントをコンパイラに提供するもので、値そのものを変更することはしません。
一方、PHPの型キャストは指定された型への値の変換を伴います。

TypeScript
let value: any = "this is a string";
// 型アサーション
let valLength: number = (value).length;
console.log(valLength); // 16
PHP8
<?php
$value =10;
var_dump($value) ; // int(10)
// 型キャスト
$bool = (boolean) $value;
var_dump($bool) ; // bool(true)
?>

ジェネリクス

TypeScript
function multiply<T>(value: T, n: number): Array<T> {
    const result: Array<T> = [];
    result.length = n;
    result.fill(value);
    return result;
}

const values = multiply<number>(-1, 3);
console.log(values); // [-1, -1, -1] 
PHP8
該当なし

補足
  • TypeScriptにはオブジェクトに対して有効なユーティリティ型もあります。
TypeScript
const userDiff: Partial<User> = {
  organization: "Future Corporation"
};

ユーティリティ型の例

  • Partial<T>: 要素が省略可能になった型
  • Readonly<T>: 要素が読み込み専用になった型
  • Required<T>: Partial<T> とは逆に、すべての省略可能な要素を必須に直した型
  • Record<K,T>: T を子供の要素に持つ Map 型のようなデータ型(Kがキー)を作成。
  • Exclude<T,U>: T の合併型から、 U の合併型の構成要素を除外した合併型を作る型

変数の比較

定義方法

TypeScript
let value = "test";
console.log(value); // "test" 
PHP8
<?php
$value = "test";
echo $value; // test
?>

補足
  • どちらの言語もthisに代入はできません(PHP 7.1.0 より前のバージョンでは、 (可変変数を使った) 間接的な代入が可能)
PHP8
<?php
$this = "value";
echo $this; // Fatal error: Cannot re-assign $this
?>
  • TypeScriptでvarを使った場合、後から宣言をするとundifined(変数の巻き上げ)になりますが、phpの変数では発生しません。
TypeScript
console.log(value); // undefined
var value;
PHP8
<?php
echo $value; // Undefined variable $value
$value;
?>

グローバル変数

TypeScript
// 制限なく関数内からグローバル変数へアクセスできます。
let globalValue = 1;  // グローバル変数

function Func()
{
    globalValue++;
}

Func();

console.log(globalValue); // 2
PHP8
<?php
// グローバル変数に関数内からアクセスするには、globalキーワードで宣言するか$GLOBALSを使用する必要があります。
$globalValue = 1;  // グローバル変数

function Func_1()
{
    global $globalValue;

    $globalValue++;
}

function Func_2()
{
    $GLOBALS["globalValue"]++;
}

Func_1();
echo $globalValue . "\n"; // 2

Func_2();
echo $globalValue; // 3
?>

補足
  • JavaScriptの場合、varをつけないとグローバル変数になりますが、TypeScriptでは宣言なしの変数は許容されません。
TypeScript
globalValue = 1;
console.log(globalValue); // lobalValue is not defined .

静的変数

静的変数はローカル関数スコープのみに存在しますが、プログラム実行がこのスコープの外で行われるようになってもその値を失いません。static変数は、再帰関数を実現する1つの手段としても使用されます。

TypeScript
該当なし
PHP8
<?php
function test()
{
    static $count = 0;

    $count++;
    echo $count;
    if ($count < 10) {
        test();
    }
    $count--;
}
test(); // 12345678910
?>

補足
  • TypeScriptで同様の処理を行う場合は、関数外に変数を宣言したり、オブジェクトのプロパティとして指定するような方法が考えられます。
TypeScript
let count = 0;

function test() {

    count++;
    console.log(count);
    if (count < 10) {
        test();
    }
    count--;
}
test(); // 12345678910

const obj = {
    cnt: 0,
    func: function() {
        this.cnt++;
        console.log(this.cnt);
        if (this.cnt < 10) {
            test();
        }
        this.cnt--;
    }
}
obj.func(); // 12345678910
// console.log(this.cnt); // Cannot read property 'cnt' of undefined
  • 静的変数には定数式の結果としての値を代入できますが、 関数呼び出しのような動的な式の結果を代入するとパースエラーになります。
PHP8
<?php
function Func_1(){
    static $int = 0;
    $int++;
    echo $int . "\n";
}
function Func_2(){
    static $int = 1+2;
    $int++;
    echo $int . "\n";
}
function Func_3(){
    static $int = sqrt(121); 
    $int++;
    echo $int;
}
Func_1(); // 1
Func_2(); // 4
Func_3(); // Constant expression contains invalid operations
?>

可変変数

可変変数では、変数名を動的にセットし使用できます。

TypeScript
該当なし
PHP8
<?php
$a = 'hello';
$$a = 'world';

echo "$a ${$a}" . "\n"; // "hello world"
echo "$a $hello" . "\n"; // "hello world"

class foo {
    var $bar = 'I am bar.';
}

$foo = new foo();
$bar = 'bar';
echo $foo->$bar; // "I am bar."
?>

定数

TypeScript
const CONSTANT = "test";
console.log(CONSTANT); // "test" 
PHP8
<?php
define( 'CONSTANT_1', 256 ); // グローバル定数
echo CONSTANT_1; // 256
?>

マジック定数

使われる場所によって値が変化する定数です。実行時に解決される通常の定数とは異なり、コンパイル時に解決されます。 これらの特別な定数は大文字小文字を区別しません。

TypeScript
該当なし
PHP8
<?php
namespace UserModel {

    class User {

        public function __construct() {
            echo "I am in " . __CLASS__ . "\n";  // I am in UserModel\User
        }

        public function showData() {
            echo "I am in " . __METHOD__ . "\n"; // I am in UserModel\User::showData
        }
    }

    $obj = new User;
    $obj->showData();
    echo __NAMESPACE__; // UserModel
}
?>

演算子の比較

代数演算子

TypeScript
const a = 4;
const a_str = "str";
const b = 2;

// 単項演算子
console.log(+a) // 4
console.log(+a_str) // NaN 
// 加算
console.log(a + b) // 6
// 減算
console.log(a - b) // 2
// 乗算
console.log(a * b) // 8
// 除算
console.log(a / b) // 2
// 剰余
console.log(a % b) // 0
// 累乗
console.log(a ** b) // 16
PHP8
<?php
$a = 4;
$a_str = "str";
$b = 2;
// 単項演算子
var_dump(+$a); // int(4)
var_dump(+$a_str); // Uncaught TypeError
// 加算
var_dump($a + $b); // int(6)
// 減算
var_dump($a - $b); // int(2)
// 乗算
var_dump($a * $b); // int(8)
// 除算
var_dump($a / $b); // int(2)
// 剰余
var_dump($a % $b); // int(0)
// 累乗
var_dump($a ** $b); // int(16)
?>

代入演算子

TypeScript
let a;
let b;

// 基本代入演算子
a = 3;
console.log(a); // 3 (代入)
a = (b = 2) + 3;
console.log(a); // 5 (加算代入)
a = 3; b = 2;
a && (a = b); // a = a && b;と同じ
console.log(a); // 2 (論理積代入)
a = 3; b = 2;
a || (a = b); // a = a || b;と同じ
console.log(a); // 3 (論理和代入)
a = 3; b = 2;
a ?? (a = b); // a = a ?? b;と同じ
console.log(a); // 3 (Null 合体代入)

// 複合代入演算子
a = 3;
a += 3; // a = a + 3と同じ
console.log(a); // 6
b = "Hello ";
b += "There!";
console.log(b); // "Hello There!"

// スプレッド構文
let c = [1, 2];
c = [...c, 3];
console.log(c); // [1,2,3]

// 分割代入
let [d, e] = [1, 2];
console.log(d); // 1
console.log(e); // 2
PHP8
<?php
// 基本代入演算子
$a = 3;
var_dump($a);  // int(3) (代入)
$a = ($b = 2) + 3;
var_dump($a); // int(5) (加算代入)
$a = 3; $b = 2;
$a && ($a = $b); // $a = $a && $b;と同じ
var_dump($a); // int(2) (論理積代入)
$a = 3; $b = 2;
$a || ($a = $b); // $a = $a || $b;と同じ
var_dump($a); // int(3) (論理和代入)
$a = 3; $b = 2;
$a ?? ($a = $b); // $a = $a ?? $b;と同じ
var_dump($a); // int(3) (Null 合体代入)

// 複合代入演算子
$a = 3;
$a += 3; // $a = $a + 3と同じ
var_dump($a); // int(6)
$b = "Hello ";
$b .= "There!";
var_dump($b); // string(12) "Hello There!"

// スプレッド構文
$c = [1, 2];
$c = [...$c, 3];
var_dump($c); // array(4) {[0]=>int(1) [1]=>int(2) [2]=>int(3)}

// 分割代入
[$d, $e] = [1, 2];
var_dump($d); // int(1)
var_dump($e); // int(2)

?>

補足
  • 変数に代入をする際、TypeScriptはJavaScriptと同様にプリミティブ型は値渡し、配列を含むオブジェクト型は参照渡しを行います。一方、PHPではオブジェクト型以外は全て値渡しになりますが、変数に&をつけることで明示的に参照渡しをすることが可能です。
TypeScript
let a = 3;
let b = a; // bにaの値を渡す
let c = [1, 2];
let d = c;  // dはcへの参照

console.log(a); // 3
console.log(b); // 3
console.log(c); // [1, 2]
console.log(d); // [1, 2]

a = 4;
c[0] = 4;

console.log(a); // 4
console.log(b); // 3
console.log(c); // [4, 2]
console.log(d); // [4, 2]
PHP8
<?php
$a = 3;
$b = $a; // $bに$aの値を渡す
$c = [1, 2];
$d = $c;  // $dに$cの値を渡す
$e = 3;
$f = &$e; // $fは$eへの参照

var_dump($a); // int(3)
var_dump($b); // int(3)
var_dump($c); // array(3) {[0]=>int(1) [1]=>int(2)}
var_dump($d); // array(3) {[0]=>int(1) [1]=>int(2)}
var_dump($e); // int(3)
var_dump($f); // int(3)

$a = 4;
$c[0] = 4;
$e = 4;

var_dump($a); // int(4)
var_dump($b); // int(3)
var_dump($c); // array(3) {[0]=>int(4) [1]=>int(2)}
var_dump($d); // array(3) {[0]=>int(1) [1]=>int(2)}
var_dump($e); // int(4)
var_dump($f); // int(4)
?>

比較演算子

TypeScript
const a: any = 1;
const a_str: any = "1";
const b: any = 2;
// 等しい
console.log(a === b);     // false
console.log(a === a_str); // false
console.log(a == a_str);  // true
// 等しくない
console.log(a !== b);     // true
console.log(a !== a_str); // true
console.log(a != a_str);  // false
// より少ない
console.log(a < b);       // true
// より大きい
console.log(a > b);       // false
// 以下
console.log(a <= b);      // true
// 以上
console.log(a >= b);      // false
PHP8
<?php
$a = 1;
$a_str = "1";
$b = 2;
// 等しい
var_dump($a === $b);     // bool(false)
var_dump($a === $a_str); // bool(false)
var_dump($a == $a_str);  // bool(true)
// 等しくない
var_dump($a !== $b);     // bool(true)
var_dump($a !== $a_str); // bool(true)
var_dump($a != $a_str);  // bool(false)
var_dump($a <> $b);      // bool(true)  PHPのみ
// より少ない
var_dump($a < $b);       // bool(true)
// より大きい
var_dump($a > $b);       // bool(false)
// 以下
var_dump($a <= $b);      // bool(true)
// 以上
var_dump($a >= $b);      // bool(false)
// $a が $b より大きいか、0か、0より小さい場合に、 それぞれ、0より大きい か、0か、0より小さい int。PHPのみ
$a = 2;$b = 1;
var_dump($a <=> $b);     // int(1)
$a = 1;$b = 1;
var_dump($a <=> $b);     // int(0)
$a = -1;$b = 2;
var_dump($a <=> $b);     // int(-1)
?>

補足
  • PHPには配列を操作・比較するための配列演算子があります。
PHP8
<?php
$a = array("a" => "apple", "b" => "banana");
$b = array("a" => "pear", "b" => "strawberry", "c" => "cherry");

// 結合
$c = $a + $b;
var_dump($c); // array(3) { ["a"]=> string(5) "apple" ["b"]=> string(6)"banana" ["c"]=> string(6) "cherry"}

$c = $b + $a;
var_dump($c); // array(3) {  ["a"]=>string(4) "pear"["b"]=>string(10) "strawberry" ["c"]=>string(6) "cherry"}

// 比較
$a = array("apple", "banana");
$b = array(1 => "banana", "0" => "apple");

var_dump($a == $b);  // bool(true)
var_dump($a === $b); // bool(false)
?>

三項演算子

TypeScript
const result_1 =  1 ? 2 : 3 ? 4 : 5;
const result_2 = (1 ? 2 : 3) ? 4 : 5; 
const result_3 = 1 ? 2 : (3 ? 4 : 5); 
console.log(result_1); // 2
console.log(result_2); // 4
console.log(result_3); // 2
PHP8
<?php
// 括弧のないネストした三項演算子はエラー
// $result_1 =  1 ? 2 : 3 ? 4 : 5;
$result_2 = (1 ? 2 : 3) ? 4 : 5; 
$result_3 = 1 ? 2 : (3 ? 4 : 5); 
// 例外として、エルビス演算子を2回重ねる場合は括弧が必要ありません。(PHPのみ)
$result_4 = 1 ?: 2 ?: 3;   
$result_5 = (1 ?: 2) ?: 3;
// echo $result_1 . "\n"; // Unparenthesized `a ? b : c ? d : e` is not supported
echo $result_2 . "\n"; // 4
echo $result_3 . "\n"; // 2
echo $result_4 . "\n"; // 1
echo $result_5 . "\n"; // 1
?>

関数の比較

関数の中の関数

PHPでは関数の中に関数を書くことができ、かつその関数がグローバルスコープで有効になります。
この使い方は意図しないグローバル汚染を発生させるため、クロージャを使用しての回避が推奨されます。

TypeScript
function foo() 
{
    console.log("foo() is called.");

    function bar() 
    {
        console.log("bar() is called.");
    }
}

foo(); // foo() is called.

// ネストされた関数にはアクセスできない
bar(); // bar is not defined 
PHP8
<?php
function foo() 
{

  echo "foo() is called.\n";

  function bar() 
  {
    echo "bar() is called.\n";
  }
}

// ここでは関数bar()はまだ定義されていないのでアクセスできない
// bar(); // Call to undefined function bar();

foo(); // foo() is called.

// PHPでは、関数やクラスはすべてグローバルスコープにあるためアクセス可能
bar(); // bar() is called.
?>

関数の引数

TypeScript
// 値渡し
let a = 1;
function foo(arg: number = 0) 
{
    ++arg;
    console.log(arg);
}

foo();           // 1
foo(a);          // 2
console.log(a);  // 1

// 参照渡し
let b = {fuga: 1, hoge: 2};
function bar(arg: {fuga: number, hoge: number}) 
{
    ++arg.fuga;
    console.log(arg);
}

bar(b);          // {fuga: 2, hoge: 2}
console.log(b);  // {fuga: 2, hoge: 2}

PHP8
<?php
// 値渡し
$a = 1;
function foo(int $arg = 0) 
{
    ++$arg;
    var_dump($arg);
}

foo();          // int(1)
foo($a);        // int(2)
var_dump($a);   // int(1)

// 参照渡し
$b = ["fuga"=> 1, "hoge"=> 2];
function bar(array &$arg) 
{
    ++$arg["fuga"];
    var_dump($arg);
}

bar($b);        // array(2) {["fuga"]=>int(2)["hoge"]=>int(2)}
var_dump($b);   // array(2) {["fuga"]=>int(2)["hoge"]=>int(2)}
?>

補足
  • 可変長引数はどちらも同じように使用できます。
TypeScript
function sum(...numbers: number[]) {
    let acc = 0;
    for(let n of numbers) {
        acc += n;
    }
    return acc;
}

console.log(sum(1, 2, 3, 4)); // 10
PHP8
<?php
function sum(int ...$numbers) {
    $acc = 0;
    foreach ($numbers as $n) {
        $acc += $n;
    }
    return $acc;
}

echo sum(1, 2, 3, 4); // 10
?>

可変関数

PHPは変数名の後に括弧が付いている場合、その値が何であろうと同名の関数を探し実行を試みます。

TypeScript
該当なし
PHP8
<?php
function foo($arg = '')
{
    echo $arg;
}

$func = "foo";
$func("test");  // test
?>

クロージャ

TypeScript
const countUp_1 = (() => {
  let count = 0;
  return (arg: number) => {
    return count += arg;
  };
})();

console.log(countUp_1(2)); // 2
console.log(countUp_1(3)); // 5


// 親スコープの変数を参照
const extr = 1;

const countUp_2 = (() => {
  let count = 0;
  return (arg: number) => {
    count += extr;
    return count += arg;
  };
})();

console.log(countUp_2(2)); // 3
console.log(countUp_2(3)); // 7
PHP8
<?php
$countUp_1 = function (int $arg): int {
  static $count = 0;
  return $count += $arg;
};

var_dump($countUp_1(2)); // int(2)
var_dump($countUp_1(3)); // int(5)

// 親スコープの変数は参照できない
$extr = 1;

$countUp_2 = function (int $arg): int {
  static $count = 0;
  $count += $extr;
  return $count += $arg;
};

var_dump($countUp_2(2)); // int(2) Undefined variable $extr
var_dump($countUp_2(3)); // int(5) Undefined variable $extr

// useを使って親スコープの変数を参照
$extr = 1;

$countUp_3 = function (int $arg) use ( $extr ): int  {
  static $count = 0;
  $count += $extr;
  return $count += $arg;
};

var_dump($countUp_3(2)); // int(3)
var_dump($countUp_3(3)); //int(7)


?>

アロー関数

TypeScript
// thisのスコープがwindow
function Person_1(arg: number) {
    this.age = arg;
    this.growOld = function() {
        // window.ageを参照している
        this.age++;
    }
}
const person_1 = new Person_1(1);
setTimeout(person_1.growOld,1000);
setTimeout(function() { console.log(person_1.age); },2000); // 1

// thisのスコープが関数内部
function Person_2(arg: number) {
    this.age = arg;
    this.growOld = () => {
        this.age++;
    }
}
const person_2 = new Person_2(1);
setTimeout(person_2.growOld,1000);
setTimeout(function() { console.log(person_2.age); },2000); // 2
PHP8
<?php

$age = 1;
$extr = 2;
$growOld = fn() => $age + $extr;
var_dump($growOld()); // int(3)

// {}を使った表記はできない

$age = 1;
$extr = 2;
$growOld = fn() => {  //syntax error
  $age + $extr;
}
?>

クラスの比較

クラスの定義

TypeScript
class SimpleClass
{
    // プロパティの宣言
    public public_prop: string = "外部スコープから参照できる";
    private private_prop: string = "外部スコープから参照できない";
    readonly readonly_prop: string = "外部スコープからは読み込みのみ"; // TypeScriptのみ
    protected protected_prop: string = "継承クラスからのみ参照できる";
    static STATIC_PROP: string = "クラスで1つだけ作成";

    // コンストラクタ
    constructor(...arg: any[]) {
        this.public_prop = arg[0];
        this.private_prop = arg[1];
        this.readonly_prop = arg[2];
        this.protected_prop = arg[3];
    }

    // アクセサ
    get getProp(): string {
        return this.private_prop;
    }

    set setProp(prop: string) {
        this.private_prop = prop;
        console.log(this.private_prop);
    }

    // メソッドの宣言
    public displayProp(prop: string): void {
        // Cannot assign to 'readonly_prop' because it is a read-only property.
        this.readonly_prop = prop; 
        console.log(this.readonly_prop);
    }

}

const simpleClass = new SimpleClass("a", "b", "c", "d");
console.log(simpleClass.public_prop); // "a"
// Property 'private_prop' is private and only accessible within class 'SimpleClass'.
console.log(simpleClass.private_prop); // "b" 
console.log(simpleClass.readonly_prop); // "c"
// Property 'protected_prop' is protected and only accessible within class 'SimpleClass' and its subclasses.
console.log(simpleClass.protected_prop); // "d"
console.log(simpleClass.STATIC_PROP); // undefined
console.log(SimpleClass.STATIC_PROP); // "クラスで1つだけ作成" 
console.log(simpleClass.getProp); // "b"
simpleClass.setProp = "b_set"; // "b_set"
simpleClass.displayProp("c_set"); // "c_set"

PHP8
<?php

class SimpleClass
{
    // プロパティの宣言
    public string $public_prop = "外部スコープから参照できる";
    private string $private_prop = "外部スコープから参照できない";
    protected string $protected_prop = "継承クラスからのみ参照できる";
    static string $static_prop = "変更可能な定数";
    const CONST_PROP = "変更不可能な定数";

    // コンストラクタ
    public function __construct(string ...$arg)
    {
        $this->public_prop = $arg["0"];
        $this->private_prop = $arg["1"];
        $this->protected_prop = $arg["2"];
        SimpleClass::$static_prop = $arg["3"];
    }

    // メソッドの宣言
    public function getProp(): string {
        return $this->private_prop;
    }

    public function setProp(string $prop): void {
        $this->private_pro = $prop;
        var_dump($this->private_pro);
    }

}

$simpleClass = new SimpleClass("a", "b", "c", "d");
var_dump($simpleClass->public_prop); // string(1) "a"
// var_dump($simpleClass->private_prop); Cannot access private property 
// var_dump($simpleClass->protected_prop); Cannot access protected property 
var_dump(SimpleClass::$static_prop); // string(1) "d"
var_dump(SimpleClass::CONST_PROP); // string(24) "変更不可能な定数"
var_dump($simpleClass->getProp()); // string(1) "b"
$simpleClass->setProp("b_set"); // string(5) "b_set"
?>

継承

TypeScript
class Animal {
    name: string;
    protected kind: string = "Animal";

    constructor(name: string) {
        this.name = name;
    }

    showName(): void {
        console.log(this.name);
    }

    protected showOriginalKind(): void {
        console.log(this.kind);
    }
}

class Dog extends Animal {
    weight: number;

    constructor(name: string, weight: number) {
        super(name);
        this.weight = weight;
    }

    showWeight(): void {
        console.log(this.weight);
    }

    showKind() : void{
        this.showOriginalKind();
    }

}

const dog = new Dog('ジョン', 20);
dog.showName();         // ジョン
// Property 'showOriginalKind' is protected and only accessible within class 'Animal' and its subclasses.(2445)
dog.showOriginalKind(); // "Animal" 
dog.showKind();         // "Animal" 
dog.showWeight();       // 20
PHP8
<?php

class Animal {
    public string $name;
    protected string $kind = "Animal";

    function __construct(string $name) {
        $this->name = $name;
    }

    function showName(): void  {
      echo $this->name . "\n";
    }

    protected function showOriginalKind() {
       echo $this->kind . "\n";
    }
}

class Dog extends Animal {
    public int $weight;

    function __construct(string $name, int $weigh) {
        parent::__construct($name);
        $this->weight = $weigh;
    }

    function showWeight(): void  {
      echo $this->weight . "\n";
    }

    function showKind() {
        $this->showOriginalKind();
    }

}

$dog = new Dog('ジョン', 20);
$dog->showName();    // ジョン
// Fatal error: Uncaught Error: Call to protected method 
// $dog->showOriginalKind();
$dog->showKind();    // "Animal" 
$dog->showWeight();  // 20
?>

デストラクタ

TypeScript
// ガベージコレクションによって自動的に処理される
PHP8
<?php
class MyDestructableClass 
{
    function __construct() {
        print "In constructor\n"; // In constructor
    }

    function __destruct() {
        print "Destroying " . __CLASS__ . "\n"; // Destroying MyDestructableClass
    }
}

$obj = new MyDestructableClass();
?>

クラスの抽象化

TypeScript
abstract class AbstractClass
{
    // 拡張クラスにこのメソッドの定義を強制する
    protected abstract getValue(): string;
    abstract prefixValue(prefix: string): string;

    // 共通メソッド
    public printOut(): void {
        console.log(this.getValue());
    }
}

class ConcreteClass extends AbstractClass
{
    protected getValue(): string {
        return "ConcreteClass";
    }

    public prefixValue(prefix: string): string {
        return `${prefix}ConcreteClass`;
    }
}

const class1 = new ConcreteClass;
const class2 = new AbstractClass;        // Cannot create an instance of an abstract class.
class1.printOut();                       // ConcreteClass
console.log(class1.prefixValue("FOO_")); // FOO_ConcreteClass

PHP8
<?php

abstract class AbstractClass
{
    // 拡張クラスにこのメソッドの定義を強制する
    abstract protected function getValue(): string;
    abstract protected function prefixValue($prefix): string;

    // 共通メソッド
    public function printOut() {
        print $this->getValue() . "\n";
    }
}

class ConcreteClass extends AbstractClass
{
    protected function getValue(): string {
        return "ConcreteClass" . "\n";
    }

    public function prefixValue($prefix): string {
        return "{$prefix}ConcreteClass" . "\n";
    }
}

$class1 = new ConcreteClass;
// $class2 = new AbstractClass; Cannot instantiate abstract class AbstractClass
$class1->printOut();                     // ConcreteClass
echo $class1->prefixValue('FOO_') ."\n"; // FOO_ConcreteClass

?>

インターフェース

TypeScript
// インターフェイス 'iTemplate1' を宣言
interface iTemplate1
{
    // CONST_VALUE = 1; Cannot find name 'CONST_VALUE'.(2304)
    vars: {[key: string]: string};
    setVariable(name: string, variable: string): void;
}

// インターフェイス 'iTemplate2' を宣言する
interface iTemplate2
{
    getVariables(): string;
}

// クラスにインターフェースを適用
class Template implements iTemplate1, iTemplate2
{
    vars: {[key: string]: string} = {};

    setVariable(name: string, variable: string): void
    {
        this.vars[name] = variable;
        console.log(this.vars);
    }

    getVariables(): string
    {
        let result = "";
        Object.keys(this.vars).forEach((key: string) => {
            result += `[${key}]: ${this.vars[key]}`;
        })

        return result;
    }

}

const myClass = new Template();
myClass.setVariable("a", "b"); // {"a": "b"} 
console.log(myClass.getVariables()); // "[a]: b" 


PHP8
<?php

// インターフェイス 'iTemplate1' を宣言
interface iTemplate1
{
    const CONST_VALUE = 1;
    // public array $vars;  Interfaces may not include member variables
    public function setVariable(string $name, string $var): void;
    public function getConst(): string;
}

// インターフェイス 'iTemplate2' を宣言する
interface iTemplate2
{
    public function getVariables(): string;
}

// クラスにインターフェースを適用
class Template implements iTemplate1, iTemplate2
{
    // const CONST_VALUE = 2; Cannot inherit previously-inherited or override constant 
    private array $vars = array();

    public function setVariable(string $name, string $var): void
    {
        $this->vars[$name] = $var;
        var_dump($this->vars[$name]);
    }

    public function getVariables(): string
    {
        static $result = "";
        foreach($this->vars as $name => $value) {
            $result .= "[{$name}]: {$value}\n";
        }

        return $result;
    }

    public function getConst(): string
    {
        return Template::CONST_VALUE;
    }
}

$myClass = new Template();
$myClass->setVariable("a", "b"); // string(1) "b"
echo $myClass->getVariables();   // [a]: b
echo $myClass->getConst();       // 1
?>

参考にしたサイト

PHP: 言語リファレンス - Manual
PHPとJavaScriptの言語比較
仕事ですぐに使えるTypeScript
【PHP8.0】PHPの三項演算子が他言語の実装に一歩近付く

テストに使用した環境

TypeScript: TS Playground
PHP tester online

27
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Yamamoto0525
6年間出版社でWEBディレクターとして経験を積んだのち、フリーランスとして活動していました。2017年に株式会社ShareDanを立ち上げ、自社サービスの開発、WEB制作、WEBコンサルティングを行っています。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
27
Help us understand the problem. What is going on with this article?