「PHP/Webアプリケーション向けの機能」の版間の差分
→列挙型: 説明の都合上、上記ではif文を書いてからブロック中でprintを使いましたが、別に事前に if文が無くてもprinttでenum変数のvalue値は表示できます。 |
→enum の関数: print "{$mode->value} \n"; |
||
708 行 | 708 行 | ||
3 |
3 |
||
</pre> |
</pre> |
||
なお、引用符中の <code>$mode->value</code> は、下記のように <code>{$mode->value}</code> と書いてもいい(2022年6月のPHP8.1の時点)。 |
|||
<syntaxhighlight lang="PHP"> |
|||
<?php |
|||
// 装備画面 |
|||
enum soubiMode: int // : int を忘れないように |
|||
{ |
|||
case buki = 1; // 武器 |
|||
case tate = 2 ; // 盾 |
|||
case kabuto = 3 ; // かぶと |
|||
} |
|||
// $a = soubiMode::tate->value ; |
|||
print("enum関数実験" . "\n") ; |
|||
function f($mode){ |
|||
print "{$mode->name} \n"; |
|||
print "{$mode->value} \n"; |
|||
} |
|||
f(soubiMode::tate); |
|||
f(soubiMode::kabuto); |
|||
?> |
|||
</syntaxhighlight> |
|||
なお、もし、たまたま文字列"$mode->value"」を表示したい場合は、単にエスケープシーケンス「\」を使って<code>print "\$mode->value \n";</code>とすれば済む。 |
|||
2022年6月8日 (水) 23:07時点における版
原理
条件分岐とは何かについては、説明しません(中学校の技術家庭科や、高校の情報科目などで習っているハズです)。
もし実用的に、PHPで条件分岐をする際は、よくある例として、ウェブサイト画面になにかの入力欄があって、その入力値をもとに条件判定することになるでしょう。
例
今回は応用例として、なにかの会員制サイトのログイン画面を考えてみます。
HTMLフォーム画面は、原理的には、下記のようになるでしょう。
- HTMLコード例
<form action="logincatchTest.php" method="post" >
ユーザー名: <input type="text" name="username1" id="box_user" ><br>
パスワード: <input type="text" name="password1" id="box_pass" ><br>
<input type="submit" value="ログイン" id="button1">
</form>
上記コードは、HTML側の画面です。
実行すると(あるいはサーバーにアップロードしてブラウザでアクセスすると)、下記のような画面になります。
- ユーザー名:
- ログイン名:
- ログイン
ボタン「ログイン」を押すと、ページが「logincatchTest.php」の生成するhtmlページに切り替わる仕組みです。(ブラウザの受け取る 切り替え先ページ のソースコードは htmlページ です。 たとい拡張子に「.php」とついていても、中身はphpではなくhtmlです。)
さらに条件分岐をするためにPHPのコードが必要です。
プログラム例として、もしユーザー名とパスワードとが正しければ、「ログイン成功」と出るプログラムを考えてみましょう。
下記のようなPHPコードをサーバー(のドキュメントルートのフォルダ、apacheなら具体的には /var/www/html)にアップロードすることになります。
- PHPのコード
<?php
if( $_POST['username1'] == "yamada" && $_POST['password1'] == "aikotoba" ){
echo "ログイン成功。<br>" ;
}
else {
echo "やりなおしてください。ログインに失敗しました。<br>" ;
echo $_POST['username1'],"と入力されました。<br>" ;
}
?>
のようなコードになります。
PHPで条件分岐を使うにはif文を使います。if文とは、「もし~~ならば、〇〇せよ」という命令を実行する文です。
いっぽう、else文は、「もし~~でなければ、〇〇せよ」という命令を実行する文です。
他の多くのプログラム言語でも、if文とelse文は同様の機能です。
説明の簡単化のため、あらかじめユーザー名とパスワードは登録されているとします。登録されているユーザー名は「yamada」、登録されているパスワード「aikotoba」(合言葉)とします。
さて、webブラウザで、上記の(PHPファイルでなく)htmlファイルのウェブページを見に行ってください。
ログイン成功。
と表示されている画面で、ブラウザのソースコード閲覧機能を使っても、
ログイン成功。<br>
としか表示されません。
ソースコードを見ても、if文は、どこにも書かれていません。(JavaScriptの文法には if文 がある。)
またPHPは、サーバーで公開しても、JavaScriptのコードには変換されないです。
このようにPHPは、サーバーで公開しても、コードを素のHTMLにしか変換しないです。
また、webブラウザで直接アドレス指定して、上記コード例の場合なら
http://localhost/logincatchTest.php
などに移動しても
やりなおしてください。ログインに失敗しました。 と入力されました。
とブラウザ画面に表示されるだけです。
また、そのwebページのソースコードをブラウザのソース閲覧機能で見ても、
やりなおしてください。ログインに失敗しました。<br>と入力されました。<br>
とhtmlソースコードが表示されるだけです。
このように、PHP側のプログラムは、一切、ブラウザ側には送られず、よってブラウザにはPHPのソースコードは表示されません。さらに、JavaScriptにも変換されてない事に注目してください。
ブラウザで表示されているソースコードは、素のhtmlタグに変換されたあとのhtmlソースコードです。
なので、パスワードのような機密情報を、冒頭のようなPHPのコードだけでも守ることができます。
実務との違い
ただし、実際の応用の際には、より慎重を期して、パスワードなどの機密情報のあるファイルは、ドキュメントルートよりも上側のフォルダに置くなどします。
そして、そのパスワードのファイルとの送受信をすることで、パスワードの成否の判定をします。
しかし本書では説明の単純化のため、冒頭のPHPコード例では、PHPコード内にパスワードの答えを含めました。
なお、一般にwebサイトにログインした場合には、ブラウザに「クッキー」といわれるログイン情報などを残す必要があります。これは setcookie()
というPHPの組み込み関数、およびPHPのスーパーグローバル変数 $_COOKIE で可能です。
また、ブラウザにクッキー変数が存在しているかどうかは、isset()というPHP関数で調べられます。
その他、実務と手本のコードの相違点として、一般の会員制サイトでは、入力ボックスに何も入力されていない段階では、「ログイン」ボタンなどを押せないようになっていますが、その機能の実装はJavaScriptで出来ます。JavaScriptのif文や、onKeyUpプロパティなどを使えば、ボタンを押せない機能を実装できます。(詳しくはwikibooks『JavaScript/イベント処理』を参照してください。)
さらにMySQLなどのデータベース・ソフトウェアを使うことで、ユーザー名やらパスワード名などの会員情報は、MySQLなどに保管します。実務では、あまり、PHPのコード内やテキストファイルなどで直接にパスワードなどを保管することは無いです。
クッキーの入れ方
クッキーを使う前に、まずはメインページを作る必要があります。
wikibooksでも、メインページに、もしログアウトしている状態もしくは未登録の状態でアクセスすれば「ログインしていません」と右上あたりに表示されているのと同様です。
- メイン画面の例
まず、私たちの自作するメインページは、おおむね下記のようなコードになるでしょう。
- メイン画面のコード(PHP)
<?php
$kukiFlag = $_COOKIE['kukites1'] ;
if(isset($kukiFlag)) {
echo "ログイン中。<br>" ;
}
else {
echo "ログインしていません。<br>" ;
}
?>
<html>
<body>
<a href="loginTest.html">ログイン</a> <br>
<br>
<p>(※ ここにメインコンテンツの表示の予定。)</p>
</body>
</html>
当然、最初にこのページを表示したとき、まだクッキーを作ってないので、「ログインしていません」と表示されます。
hrefタグは、文章や画像などにクリック時に別ページにリンクする機能をつけるためのプロパティです。HTMLのどの入門書にも hrefタグは解説が書いてあるので、本PHP教科書ではhrefの説明は省略します。
さて、上記メインページから、ログイン画面に映ります。
- ログイン画面の例
ログイン画面のコードは、すでに前の節で作成したのと同じで構いません。下記にコードを再掲します。
- ログイン画面のコード
<form action="logincatchTest.php" method="post" >
ユーザー名: <input type="text" name="username1" id="box_user" ><br>
パスワード: <input type="text" name="password1" id="box_pass" ><br>
<input type="submit" value="ログイン" id="button1">
</form>
ログイン画面では、まだクッキーは作成しないです。
クッキーを作成する時期は、ログインに成功したあとです。
- ログイン成否の確認画面の例
さて、ログインの成否の確認画面が、下記のように変わります。
<form action="logincatchTest.php" method="post" >
<?php
if( $_POST['username1'] == "yamada" && $_POST['password1'] == "aikotoba" ){
setcookie("kukites1", "123",time()+ 30 ); // 30秒の保存期間
echo "ログイン成功。<br>" ;
}
else {
echo "やりなおしてください。ログインに失敗しました。<br>" ;
echo $_POST['username1'],"と入力されました。<br>" ;
}
?>
<html>
<body>
<br>
<a href="kukiTesM.php">メインページに戻る</a> <br>
</body>
</html>
setcookie()関数で、クッキーをブラウザに与えます。
書式は
setcookie(クッキー名, 値, time() + 時間 );
です。
上記コードでは、安全のため、30秒の経過でクッキーが消失するようにしています。
つまり
setcookie(クッキー名, 値, time() + 保存の秒数 );
という書式です。
なので、もし
setcookie(クッキー名, 値, time() + 30 );
なら、30秒でクッキー消失します。
たとえば
setcookie(クッキー名, 値, time() + 60*60*24*2 );
なら、2日間、クッキーが残ります。ぴったり2日間経過した直後の瞬間に、クッキーが消えます。
30秒でクッキー消失します。
さて、上記コードでは、 またクッキーとは別に、ログイン成功したあとにメインページに戻る機能も必要なので、hrefタグでメインページに戻るリンク文も追加しています。
なお、上記コードでは実はログイン失敗しても「メインページに戻る」リンク文が表示されるのですが、説明の単純化のため、上記コードのままにします。(クッキー以外のif文が増えると、説明がややこしくなるので。)
- 実験例
では、上記3個のページのコードすべてが完成したら、ローカルサーバーにアップロードしてみて、ブラウザからメインページにアクセスしてみてください。
そして、ログインしてください。ユーザー名「yamada」、パスワード「aikotoba」です。
ログイン成功したら、すぐに30秒以内にリンク文を押して、メインページに移動してください。
そして、メインページの表示中に、キーボードのファンクションキーのF5ボタンを押して、リロードしてください。
30秒以内なら、クッキーが保存中なので「ログイン中」とメインページで表示されます。
さて、F5ボタンを飽きるまで、何回か押して、なんどかリロードして、いろいろと試してください。
もし30秒経過後にリロードすると、もはやクッキーが消失しているので「ログインしていません」と表示されます。
- 備考
実際のwebブラウザでは、PHPのコードで指定した値(上記コードの場合なら「123」)のほかにも、さまざまな情報がwebブラウザ側で自動的に保存される。アクセス日時、作成日時、有効期限などの情報や、諸設定に必要な情報が保存されている。
この事を確認するには、Firefoxの場合、通常版Firefoxでは確認が不可能であるので、開発者用の Firefox Developer Edition を使う必要がある。(通常版 Firefox では、クッキー名の一覧は見れるが、しかしクッキー内部を見ることができない。)
開発者用Frirefoxでの 三本アイコン > 「ウェブ開発」>「ストレージインスペクター」で、クッキーのブラウザに保管された状態での中身を見ることができる。 (まぎらわしいことに「インスペクター」というメニューも近くにあるが、それではなく、「ストレージインスペクター」を選ぶ必要がある。)
なお Linux の場合 インストールは不要で、単に Firefox 公式サイトからダウンロードしてきて解凍し、ファイル内にある実行ファイル「Firefox」をクリックすれば、開発版ブラウザが起動する。
※備考: クッキーのセキュリティ対策など
クッキーのデータはネットワーク経由で受け渡しされる。なので、念のために「盗聴」(データの覗き見)を第三者にされている事態も想定しておき、そのためクレジットカードの番号やパスワードなどといった機密情報については、けっしてクッキーでは受け渡しをしていはいけない。[1] [2]
よってクッキーでは、パスワードなどのある認証サイトのキーだけを受け渡しするようにするなどの注意が必要である。
また、もしもクッキーの内部に、ログインについてのYesまたはNo のような文字列を入れてその文字を読み取るようなログイン方式だと、不正ユーザーによって、文字列を書き換えされて不正ログインされる可能性があるので、別の方式にする必要がある[3]。
単に、ログインのキーだけを渡すのが良いだろう。
なお、一般にブラウザのクッキーの保管の容量には4キロバイトまでと制限があり[4]、もし容量オーバーすると古いクッキーから順に削除されていく方式である。
このため、大きな情報はクッキーには納めてはいけないし、納められない[5]。
アカウント設定などの情報は、クッキーには含めないでおくのが、実務的にもマナー的にも望ましいだろう。アカウント設定などはサーバー側でデータベースなどで保管しておこう。セキュリティ的にも、サーバー側で保管するのが安全である。
厳密な等価演算子===
if文などで等価を判定するさいのイコール演算子は、
PHPでは少なくとも2種類ある。
イコール記号2個の==
と、
イコール記号3個の===
である。
イコール記号2個では、型を区別しない。
イコール記号3個では、型を区別する。
なお、イコール1個だけでは単なる代入命令であるので、等価演算子としては機能しない。
厳密でないイコール2個
イコール記号2個の==
で評価した場合、
下記コード、
<?php
$a =1; //
if($a==1) {
print ("数字<br>") ;
}
if($a=="1") {
print ("文字列<br>") ;
}
?>
を実行すると
数字 文字列
のように、両方の場合にヒットしてしまいます。
$a =1;
を代わりに$a ="1";
と宣言しても同様に両方ヒットの結果になります。
このように、数値として定義したつもりの変数が文字列としても解釈されたりする仕様は、便利な場合もありますが、ときには予想外の動作を引きおこすので使いたく無い場合もあります。そこで、下記のように、イコール3個の厳密な判定を行う等価演算子が用意されています。
厳密なイコール3個
イコールの比較のさいの、型についての予想外の動作を防ぐための、より厳密な比較をできるイコール3個の演算子があります。
if文などでの比較の際、イコール記号3個の===
で評価すると、数値型と文字列型とを区別して比較し、型も含めて同じ場合にだけ真になります。
<?php
$a = 1;
if($a === 1) {
print ("数字<br>") ;
}
if($a === "1") {
print ("文字列<br>") ;
}
?>
- 実行結果
数字
このように、イコール3個で、型を厳密に区別して、等価かどうかを評価します。
- 余談
余談ですが、PHPは変数に値を代入する際、じつは型の情報も保管しています。なので、上記のように厳密評価も必要に応じて行えるわけです。
match式
2020年11月リリースされたPHP8から条件分岐にmatch式(match expression)が加わりました。
match式は、下記のように、引数の数値に基づいて場合分けをするときに、使います。
コード例
<?php
$x = 1;
match ($x) {
0 => print("aaaa" . "\n"),
1 => print("qwer" . "\n"),
2 => print("test" . "\n") ,
};
?>
- (※ PHP 8.1.6 で、2Fedora 36 にて 2022年05月24日に動作確認ずみ。)
実行結果
qwer
なお、switch文との違いとして、match式ではbreakが不要です。
macth式の方が便利なので、今後はmatch式の用例が増えると思います。
なお、match「式」(expression)とは言いますが、数学の「式」(equation, formula)とは別物です。プログラミング用語の"expression"とは、単に、関数や変数や演算子などを合わせた表現にすぎません。
match式では、下記のように、戻り値を変数(下記コードでは変数 $message )に入れることもできます。
コード例
<?php
$x = 1;
$message = match ($x) {
0 => "aaaa",
1 => "qwerffff",
2 => "test" ,
};
print($message . "\n");
?>
- (※ PHP 8.1.6 で、2Fedora 36 にて 2022年05月24日に動作確認ずみ。)
実行結果
qwerffff
なお、match式の引数の比較は、厳密なイコール3個の等号===で比較判定されています。
一方、switch式の引数の比較は、曖昧なイコール2個の等号==で比較判定されています。
- default
match式における条件わけの引数の値を下記のように default
で、どれにもヒットしなかった場合の結果を示せます。
コード例
<?php
$x = 5;
match ($x) {
0 => print("aaaa" . "\n"),
1 => print("qwer" . "\n"),
default => print("ddd" . "\n") ,
2 => print("test" . "\n") ,
};
?>
実行結果
ddd
上記コード例では動作原理の説明のために意図的に default を最後には書きませんでしたが、普通のプログラミングでは下記のように default は最後に書きます。
コード例
<?php
$x = 5;
match ($x) {
0 => print("aaaa" . "\n"),
1 => print("qwer" . "\n"),
2 => print("test" . "\n") ,
default => print("ddd" . "\n") ,
};
?>
実行結果は同様にddd
です。
列挙型
概要
PHP8.1から列挙型(enum)が使えます。
enum とは何かの説明が難しいのでコードと実行例を先に示します。
コード例
<?php
enum gameMode
{
case map;
case menu;
case battle;
}
$a = gameMode::map ;
$b = gameMode::menu ; // このサンプルコードでは$bは以降は未使用。参考のため書いている。
if ($a == gameMode::map ){ print("今マップ画面を操作中です。" . "\n") ; }
if ($a == gameMode::menu ){ print("今メニュー画面を操作中です。" . "\n") ; }
?>
- (※ 2022年05月21日にPHP 8.1.6 を、Fedora 36上で動作確認。)
実行結果
今マップ画面を操作中です。
PHP以外のどの言語でも、enumは条件分岐以外にも使えますが、条件分岐の応用が比較的に有名です。
なお、C言語などのよその言語ではswitch文のラベル番号の省略などの応用にenumを使うのが有名なのですが、しかしPHPでは現状、整数の自動的な割り当ての機能の仕様が不透明なので、switch文への応用の可能性も不明です。
- ネストは不明
なお、enumの中にenumを入れるという、enumの入れ子(ネスト)については、現状のところは動作例は不明ですし仕様も不明です。おそらくenumのネストについては非サポートです。そもそもC言語自体に現状、enumのネストがない状態です(2021年に記述)。
enum型と変数の同時宣言の実装状況
enum を宣言する際、形式的にはenum型の変数を下記コードのように同時に宣言できますが、果たして本当にenum型変数として認識しているかは不明です(enum の宣言とは独立して、単に独立の「$a;」という命令文だと認識している可能性がある。クラス class を転用して enum を実装しているようだが(エラー文などで "class" を見かけるので)、そもそもPHPのクラスの宣言では、クラスの型とアクセス用変数とは同時宣言できない(インスタンス化が必要なので) )。しかも2個以上のアクセス用変数の宣言には対応しておらず1個しか宣言できません。
コード例
<?php
enum gameMode
{
case map;
case menu;
case battle;
} $a;
// 最後の行では $a,$b; と2個以上で宣言するとコンパイルエラー
$a = gameMode::battle ;
$b = gameMode::menu ; // このサンプルコードでは$bは以降は未使用。参考のため書いている。
if ($a == gameMode::battle ){ print("今、戦闘画面を操作中です。" . "\n") ; }
if ($a == gameMode::menu ){ print("今メニュー画面を操作中です。" . "\n") ; }
?>
- (※ 2022年05月21日にPHP 8.1.6 を、Fedora 36上で動作確認。)
実行結果
今、戦闘画面を操作中です。
なお、PHP7以降の近年のPHPは、限定的ですが型の機能を導入することで安全な動作を保障できるように開発していく方針になっています。関数など一部の機能では、最新版のPHPでは、すでに型が使えます。なのでもしかたらenumについても型が使えるようになるのかもしれない可能性もありますが、現状ではまだ可能性です。enumの型を使えない可能性も充分にありますので、早合点しないようにしましょう。
スカラ値つきのenum
enum に整数型(int)または文字列型(string)の値をつけることが可能です。
enumの要素に関連づけられた数値にアクセスする場合、下記コードのように ->value
が必要です。これは整数型でも文字列型でも、ともに ->value
です。なお int も string もつけず ->value
でアクセスするのは非推奨であり、警告されます。(Warning: Undefined property:
などの文字列。またintを宣言せずにenumを使用した場合に各要素の代入される数値は現状、0,1,2,3・・・ではないです。なので、値つき宣言して無い場合には、代入値の順番や大小などは一切当てにしないのが安全でしょう。)
そのほか、enum要素名(下記コード例でなら"buki"や"tate"の部分のこと)にアクセスする場合には ->name
になります。現状、->name
プロパティはint宣言やstring宣言とは無関係に使えます。
つまり valueというプロパティは、enumをintまたはstringに関連づけて宣言しおえた段階までに自動で生成されます。よって、プログラマー側でvalueプロパティを宣言する必要はありません。nameプロパティも宣言の必要は無いです。
また、value
以外のデタラメな文字列(たとえば「vabqe」)とかのプロパティを書いてもエラーになります。
このように、あらかじめ value および name というプロパティがenumでは決まっています。
コード例
<?php
// 装備画面
enum soubiMode: int // : int を忘れないように
{
case buki = 1; // 武器
case tate = 2 ; // 盾
case kabuto = 3 ; // かぶと
}
$a = soubiMode::tate->value ;
if ($a == 2 ){
print("{$a}番データベースを編集。" . "\n") ;
}
if ($a == 3 ){
print("こっちには来てない。" . "\n") ;
}
?>
- (※ 2022年05月21日にPHP 8.1.6 を、Fedora 36上で動作確認。)
実行結果
2番データベースを編集。
なお、C言語やPythonなど他の一部の言語では自動的にスカラー値を付ける機能があるのですが、しかしPHPでは現状ではまだそのような機能や仕様は知られていません(2021年に本文を記述)。なおC言語ではこのような機能があるので、switch文のラベルの番号付けの省略としてenumを応用する例が有名です。
さて、enumで定義した整数値を使って計算したい場合、下記のように->value
プロパティで代入値にアクセスする必要があります。
コード例
<?php
// 装備画面
enum soubiMode: int // : int を忘れないように
{
case buki = 5; // 武器
case tate = 6 ; // 盾
case kabuto = 7 ; // かぶと
}
$a = soubiMode::kabuto->value - 4 ; // 7-4 = 3
// 現状では if 文で事前にenum格納した変数を呼び出さないとエラーになる。
if ($a == 3 ){
print("{$a}番データベースを編集。" . "\n") ;
}
if ($a == 4 ){
print("こっちには来てない。" . "\n") ;
}
?>
- (※ 2022年05月21日にPHP 8.1.6 を、Fedora 36上で動作確認。)
実行結果
3番データベースを編集。
説明の都合上、上記ではif文を書いてからブロック中でprintを使いましたが、別に事前に if文が無くてもprinttでenum変数のvalue値は表示できます。
<?php
// 装備画面
enum soubiMode: int // : int を忘れないように
{
case buki = 1; // 武器
case tate = 2 ; // 盾
case kabuto = 3 ; // かぶと
}
print("valueごと定義\n");
$a = soubiMode::tate->value ;
print($a);
print("\n");
print("print側でvalue指定\n");
$b = soubiMode::buki ;
print($b->value);
print("\n");
?>
- (※ 2022年06月にFedora 36上で動作確認。)
実行結果
valueごと定義 2 print側でvalue指定 1
enum の関数
enum は関数の引数にすることもできる。(関数について詳しくはPHP/入門/関数とはで扱う。)
<?php
// 装備画面
enum soubiMode: int // : int を忘れないように
{
case buki = 1; // 武器
case tate = 2 ; // 盾
case kabuto = 3 ; // かぶと
}
// $a = soubiMode::tate->value ;
print("enum関数実験" . "\n") ;
function f($mode){
print "$mode->name \n";
print "$mode->value \n";
}
f(soubiMode::tate);
f(soubiMode::kabuto);
?>
- (※ 2022年05月21日にPHP 8.1.6 を、Fedora 36上で動作確認。)
実行結果
enum関数実験 tate 2 kabuto 3
なお、引用符中の $mode->value
は、下記のように {$mode->value}
と書いてもいい(2022年6月のPHP8.1の時点)。
<?php
// 装備画面
enum soubiMode: int // : int を忘れないように
{
case buki = 1; // 武器
case tate = 2 ; // 盾
case kabuto = 3 ; // かぶと
}
// $a = soubiMode::tate->value ;
print("enum関数実験" . "\n") ;
function f($mode){
print "{$mode->name} \n";
print "{$mode->value} \n";
}
f(soubiMode::tate);
f(soubiMode::kabuto);
?>
なお、もし、たまたま文字列"$mode->value"」を表示したい場合は、単にエスケープシーケンス「\」を使ってprint "\$mode->value \n";
とすれば済む。
- 余談
実は2021年にPHP本体にenumの機能が使われるよりもずっと前から、 コミュニティ外部のライブラリである Laravel や Symphony などの「フレームワーク」に前々からenumが存在していました(ただし書式は違っていたが)。
つまり結果的には、フレームワークで有用性の実証された機能が、2021年にPHP本体にも取り入れられたことになります。