[PHP] 13 ブラウザ起動と同時にDeveloper Toolsを開く Chrome, Firefox

[M1 Mac, Big Sur 11.7.2, PHP 8.2.1, MySQL 8.0.31, noFramework]

ブラウザ起動と同時にDeveloper Toolsを開くようにしました。

Firefoxは下側に開きます。後から右側に変えられるものの私には非常に使いにくいのでChromeに戻ってきました。

<?php
require_once ('../../composer/vendor/autoload.php');
use Facebook\WebDriver\Chrome\ChromeDriver;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\Remote\DesiredCapabilities;

# MySQLサーバ起動判定
exec('mysqladmin ping', $out, $ret);
$out_str = $out[0];
$out_str2 = sprintf("out_str: %s", $out_str);
echo $out_str2;

if(strpos($out[0],'alive') === false){
    exec('mysql.server start');
}else{
    print 'MySQLは起動しています';
    echo "\n";
}

# Webサーバ起動
exec('php -S localhost:8890 -t "/code/PHP/projects/02_mysql_searcher39" > /dev/null &');

# chromedriverのパス設定
$driverPath = realpath("/opt/homebrew/Caskroom/chromedriver/109.0.5414.74/chromedriver");
putenv("webdriver.chrome.driver=" . $driverPath);

# オプション指定
$options = new ChromeOptions();
$options->addArguments(['--width=400','--height=400','--auto-open-devtools-for-tabs']);

$caps = DesiredCapabilities::chrome();
$caps->setCapability(ChromeOptions::CAPABILITY, $options);

# Chromeを起動しphpスクリプトを実行
$driver = ChromeDriver::start($caps);
$driver->get('http://localhost:8890/input.php');

<以下略>
<?php
require_once ('../../composer/vendor/autoload.php');
use Facebook\WebDriver\Firefox\FirefoxDriver;
use Facebook\WebDriver\Firefox\FirefoxOptions;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\Remote\DesiredCapabilities;

# MySQLサーバ起動判定
exec('mysqladmin ping', $out, $ret);
$out_str = $out[0];
$out_str2 = sprintf("out_str: %s", $out_str);
echo $out_str2;

if(strpos($out[0],'alive') === false){
    exec('mysql.server start');
}else{
    print 'MySQLは起動しています';
    echo "\n";
}

# Webサーバ起動
exec('php -S localhost:8890 -t "/code/PHP/projects/02_mysql_searcher38" > /dev/null &');

# geckodriverのパス設定
putenv('webdriver.gecko.driver=/usr/local/bin/geckodriver');

# オプション設定
$desiredCapabilities = DesiredCapabilities::firefox();
$firefoxOptions = new FirefoxOptions();
$firefoxOptions->addArguments([ '--width=600','--height=400','-devtools']);
$desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions);

# Firefoxを起動しphpスクリプトを実行
$driver = FirefoxDriver::start($desiredCapabilities);
$driver->get('http://localhost:8890/input.php');

<以下略>

[PHP] 12 Firefoxで自動操作

[M1 Mac, Big Sur 11.7.2, PHP 8.2.1, MySQL 8.0.31, noFW]

Webアプリ自動操作のブラウザをFirefoxに変えてみました。

Developer Toolsは最初から日本語対応です。警告の内容が細かく、Chromeに比較して開発者にやさしい仕様に見えました。

しばらくFirefoxを試してみます。

<?php
require_once ('../../composer/vendor/autoload.php');
use Facebook\WebDriver\Firefox\FirefoxDriver;
use Facebook\WebDriver\Firefox\FirefoxDriverService;
use Facebook\WebDriver\Firefox\FirefoxOptions;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\Remote\DesiredCapabilities;

# MySQLサーバ起動判定
exec('mysqladmin ping', $out, $ret);
$out_str = $out[0];
$out_str2 = sprintf("out_str: %s", $out_str);
echo $out_str2;

if(strpos($out[0],'alive') === false){
    exec('mysql.server start');
}else{
    print 'MySQLは起動しています';
    echo "\n";
}

# Webサーバ起動
exec('php -S localhost:8890 -t "/code/PHP/projects/02_mysql_searcher37" > /dev/null &');

# geckodriverのパス設定
putenv('webdriver.gecko.driver=/usr/local/bin/geckodriver');

# オプション設定
$desiredCapabilities = DesiredCapabilities::firefox();
$firefoxOptions = new FirefoxOptions();
$firefoxOptions->addArguments([ '--width=600','--height=400']);
$desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions);

# Firefoxを起動しphpスクリプトを実行
$driver = FirefoxDriver::start($desiredCapabilities);
$driver->get('http://localhost:8890/input.php');

# Firefox表示待機
$driver->wait(10)->until(
    WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::id("form_submit"))
);

sleep(1);
# 自動入力
$driver->findElement(WebDriverBy::id("input1"))->sendKeys("lpw");
sleep(1);
$driver->findElement(WebDriverBy::id("input2"))->sendKeys("Email");
sleep(1);
$driver->findElement(WebDriverBy::id("input3"))->sendKeys("@yahoo.co.jp");
sleep(1);
# 確定ボタン押下
$driver->findElement(WebDriverBy::id("button"))->click();

# search.phpへの遷移待機
$driver->wait(10)->until(
    WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::id("clockTime"))
);

sleep(1);
# 検索ボタン押下
$driver->findElement(WebDriverBy::id("button2"))->click();

?>

参考サイト

[PHP] 11 日付取得とglob関数

[M1 Mac, Big Sur 11.7.2, PHP 8.2.1, MySQL 8.0.31, noFW]

MySQL検索結果のCSVファイル名に日付と通し番号を付けました。

タイポしてしまったスクリプトが停止せずに動いたことには驚きました。PHPは変数宣言が不要なので、式の右辺に突然未宣言の変数を書いてもエラーになりません。

PythonやVBAよりも文法がゆるい言語があるとは知りませんでした。

# 正しいスクリプト
$csv_counts = $csv_counts + 1;

# タイポしたスクリプト(エラーにならない)
$csv_counts = $csv_unts + 1;
# 今日の日付取得
$objDateTime = new DateTime();
$date = $objDateTime->format('ymd');
// echo nl2br($date."\n");

# 今日作成csvファイルをカウント
$num_file;
foreach (glob("/projects/_csv/*.csv") as $filename) {
    // echo nl2br($filename."\n");
    if (strpos($filename,$date) == true){
        $num_file +=1;
    }
}
// echo nl2br($num_file."\n");
$new_num = $num_file + 1;

# csvファイルパス作成
$prefix_csv = '/projects/_csv/';
$file_csv = $prefix_csv.$date."_mysql_".$new_num.".csv";
// echo nl2br($file_csv."\n");

# csvファイル作成
$fp_csv = fopen($file_csv, 'a');

# MySQL検索
$tbls_num = 1;
$counts = 0;
$csv_counts = 0;
foreach ($tbls as $tbl) {
    $query = sprintf("SELECT * FROM `%s` WHERE `%s` LIKE '%%%s%%'", implode($tbl), $field2, $key2);
    $result = $conn->query($query);
    $rows = $result->fetch_all(MYSQLI_ASSOC);

    # 件数カウント
    $count = count($rows);
    $counts = $counts + $count;

    # csv化(1000件以下)
    for($i = 0, $size = count($rows); $i < $size; ++$i){
        if ($csv_counts < 1000){
            $row_str = $rows[$i];
            fputcsv($fp_csv, $row_str);

            $csv_counts = $csv_counts + 1;
        } 
    }
    $tbls_num = $tbls_num + 1;
}
fclose($fp_csv);

$conn->close();
exec('mysql.server stop');

[PHP] 10 homeディレクトリの取得

[M1 Mac, Big Sur 11.7.2, PHP 8.2.1, MySQL 8.0.31, noFW]

プログラムからクライアント側のローカルファイルを読み込むことは当然厳禁ですが、ファイル出力は可能です。

MySQLの検索結果CSVファイルをhomeディレクトリに出力させてみました。UTF-8であればPythonに比べてCSVファイルの作成は簡単です。

今更ですがPHPでは文字列をピリオドで結合するんですね。当たり前のようにプラスでつないで気付くまでエラーに見舞われていました。この結合方式はPerl由来なのでしょうか。

$db2 = $_COOKIE["db"];
$field2 = $_COOKIE["field"];
$key2 = $_COOKIE["key"];

# MySQLにログイン
$conn = new mysqli('localhost', 'root', 'root');
if (!$conn){
    die("MySQL接続に失敗しました");
} else {
    // print 'MySQL接続に成功しました';
}

# データベース選択
$select_db = sprintf("use %s", $db2);
$select = $conn->query($select_db);

# 全テーブル取得
$tbls_query = $conn->query('show tables;');
$tbls = $tbls_query->fetch_all(MYSQLI_ASSOC);

$tbls_count = count($tbls);

# homeディレクトリ取得
ob_start();
echo posix_getpwuid(posix_geteuid())['dir'];
$home_dir = ob_get_clean();
$home_dir = strtolower($home_dir);

# csvファイルパス作成
$suffix_csv = '/MySQL/test.csv';
$file_csv = $home_dir.$suffix_csv;

# csvファイル作成
$fp_csv = fopen($file_csv, 'a');

$tbls_num = 1;
$counts = 0;
foreach ($tbls as $tbl) {
    $query = sprintf("SELECT * FROM `%s` WHERE `%s` LIKE '%%%s%%'", implode($tbl), $field2, $key2);
    $result = $conn->query($query);
    $rows = $result->fetch_all(MYSQLI_ASSOC);

    # 件数カウント
    $count = count($rows);
    $counts = $counts + $count;

    # csv化(1000件以下)
    if ($counts <= 1000){
        for($i = 0, $size = count($rows); $i < $size; ++$i){
            $row_str = $rows[$i];
            fputcsv($fp_csv, $row_str);
        }
    }
    
    $tbls_num = $tbls_num + 1;
}
fclose($fp_csv);

$conn->close();
exec('mysql.server stop');

[PHP] 09 POSTリクエストでsubmitしたformの内容を取得する cookie

[M1 Mac, Big Sur 11.7.2, PHP 8.2.1, MySQL 8.0.31]

submitしたformの内容を次ページに表示させても数秒で消えてしまいます。

そこでfilter_inputメソッドで取得した変数をcookieに一旦格納してから、使用時にこれを取り出しました。ただcookieに入れてしまうとfilter_inputメソッドで取得した際の変数名($dbなど)も問題なく使えるようになります。

なぜこのようなことになるのかよく分かりません。ガベージコレクション絡みでしょうか。

$db = filter_input(INPUT_POST, 'db');
$field = filter_input(INPUT_POST, 'field');
$key = filter_input(INPUT_POST, 'key');

setcookie('db', $db);
setcookie('field', $field);
setcookie('key', $key);

if(isset($_POST['button2'])){
    $db2 = $_COOKIE["db"];
    $field2 = $_COOKIE["field"];
    $key2 = $_COOKIE["key"];

    # MySQLにログイン
    $conn = new mysqli('localhost', 'root', 'root');
    if (!$conn){
        die("MySQL接続に失敗しました");
    } else {
        // print 'MySQL接続に成功しました';
    }

    <変数はクエリで使用>

<中略>

<table id="search_table" border="2">
        <tr>
            <td>データベース</td>
            <td id="input1-2" width="180" name="db2"><?php echo $db; ?></td>
        </tr>
        <tr>
            <td>フィールド</td>
            <td id="input2-2" width="180" name="field2"><?php echo $field; ?></td>
        </tr>
        <tr>
            <td>検索語</td>
            <td id="input3.2" width="180" name="key2"><?php echo $key; ?></td>
        </tr>
</table>

[PHP] 08 MySQLアプリの自動操作 php-webdriver

[M1 Mac, Big Sur 11.7.2, PHP 8.2.1, MySQL 8.0.31]

製作中のWebアプリをphp-webdriverで自動操作しています。

プログレスバーの方はcookieやセッション変数を使っても解決しませんでした。cookieはブラウザをリロードするまで更新しないので今回のような即時反映には向かないです。

PHPからJavaScriptへの変数受け渡しも反映が遅くて使えません。

<?php
require_once ('../../composer/vendor/autoload.php');
use Facebook\WebDriver\Chrome\ChromeDriver;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Facebook\WebDriver\WebDriverBy;

# MySQLサーバ起動判定
exec('mysqladmin ping', $out, $ret);
$out_str = $out[0];
$out_str2 = sprintf("out_str: %s", $out_str);
echo $out_str2;

if(strpos($out[0],'alive') === false){
    exec('mysql.server start');
}else{
    print 'MySQLは起動しています';
    echo "\n";
}

# Webサーバ起動
exec('php -S localhost:8890 -t "/Volumes/DATA_m1/code/PHP/projects/02_mysql_searcher23" > /dev/null &');

# chromedriverのパス設定
$driverPath = realpath("/opt/homebrew/Caskroom/chromedriver/109.0.5414.74/chromedriver");
putenv("webdriver.chrome.driver=" . $driverPath);

# Chromeを起動しphpスクリプトを実行
$driver = ChromeDriver::start();
$driver->get('http://localhost:8890/input.php');

# Chrome表示待機
$driver->wait(10)->until(
    WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::id("form_submit"))
);

sleep(1);
# 自動入力
$driver->findElement(WebDriverBy::id("input1"))->sendKeys("lpw");
sleep(1);
$driver->findElement(WebDriverBy::id("input2"))->sendKeys("Email");
sleep(1);
$driver->findElement(WebDriverBy::id("input3"))->sendKeys("@yahoo.co.jp");
sleep(1);
# 入力ボタン押下
$driver->findElement(WebDriverBy::id("button"))->click();

# search.phpへの遷移待機
$driver->wait(10)->until(
    WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::id("clockTime"))
);

sleep(1);
# チェックボックス押下
$driver->findElement(WebDriverBy::id("checkBox"))->click();
# 検索ボタン押下
$driver->findElement(WebDriverBy::id("button2"))->click();

?>

[JavaScript] 09 cookieの使用

[M1 Mac, Big Sur 11.7.2]

publicな変数の扱い方が全く分からないので、苦肉の策としてcookieに変数を格納し共有することにしました。

PHP、JavaScript、HTMLが混在するスクリプト内、スクリプト間でどうやって変数の受け渡しをしているのでしょうか。この点においてはC++やJavaよりも難易度が相当高いように思います。

プログレスバーについてもcookieを使って何とかできないか探っていきます。

MySQL検索終了時
<html>
<?php
# MySQLにログイン
$conn = new mysqli('localhost', 'root', 'root');
if (!$conn){
	die("MySQL接続に失敗しました");
} else {
	// print 'MySQL接続に成功しました';
}

$dbs_query = $conn->query('show databases;');
$dbs = $dbs_query->fetch_all(MYSQLI_ASSOC);

$dbs_count = count($dbs);

?>
<head>
	<meta charset="utf-8"/>
	<title>MySQL SEARCHER</title>
	<link rel="shortcut icon" href="favicon.ico">
	
</head>
<body>
データベース、カラム名、検索語を入力してください
<form id="form_submit" action="output.php" method="post">
	<table border="1">
		<tr>
			<td>DB</td>
			<td><input id="input1" type="text" name="db"></td>
		</tr>
	</table>
	<tr>
	<?php
	$num = 0;
	foreach ($dbs as $db) {
		if ($num == 0){
			echo "DB : ";
		}
		echo implode($db);
		if ($num != $dbs_count - 1){
			echo ", ";
		}
		$num = $num + 1;
		
	}
	?>
	</tr>
	<table border="1">
		<tr>
			<td>COL</td>
			<td><input id="input2" type="text" name="col"></td>
		</tr>

	</table>
	<table border="1">
		<tr>
			<td><input id="input3" type="text" name="key"></td>
			<td colspan="2" align="center">
			<input id="button" type="submit" value="検索">
			</td>
		</tr>
	</table>
</form>

<progress class="progress" value="0" id="pb">
</progress>
<br>
<br>

<span id="elapsedTime"></span>
<br>

<span id="clockTime"></span>
<script type="text/javascript">
	function set2fig(num) {
		var ret;
		if( num < 10 ) { ret = "0" + num; }
		else { ret = num; }
	return ret;
	}

	function showTime() {
		var now = new Date();
		var nowhour = set2fig(now.getHours());
		var nowminutes = set2fig(now.getMinutes());
		var nowseconds = set2fig(now.getSeconds());
	
		var clockTime = "現在時刻 " + nowhour + ":" + nowminutes + ":" + nowseconds;
		console.log(clockTime);
		document.getElementById("clockTime").innerHTML = clockTime;
    
    	return clockTime;
  	}
	
	setInterval('showTime()', 1000);
</script>
<br>

<span id="loadCount"></span>
<script language="javascript" type="text/javascript">
	var cnt;        // 読込回数

	function getCookieValue(key) {
		const cookies = document.cookie.split(';');
		for (let cookie of cookies) {
			var cookiesArray = cookie.split('='); 
			if (cookiesArray[0].trim() == key.trim()) { 
				return cookiesArray[1];
			}
		}
		return '';
	}

	// cookie使用可否確認
	if (navigator.cookieEnabled){
		console.log("cookie使用可能");
		
		// 読込回数をHTMLに表示
		if (!document.cookie){
			document.getElementById("loadCount").innerHTML = "1回目の読込";
			document.cookie="counts=1;";  
		}
		else{
			try{
				const cnt_ck = getCookieValue("counts");
				// 読み込み回数をインクリメント
				cnt = parseInt(cnt_ck) + 1;
				document.getElementById("loadCount").innerHTML = cnt + "回目の読込";
				
				//cookieに読込回数書き込み
				document.cookie="counts=" + cnt + ";";
			}
			catch(e){
				document.write("読込回数の取得に失敗しました。");
			}
		}
	}
	else{
		document.write("cookieが使用できません。");
		console.log("cookie使用不可");
	}
		
	if (cnt != 2){
		var now = new Date();
		
		function showElapsedTime() {
			var now2 = new Date();
			const eTime = Math.floor((now2 - now)/1000);
			console.log(eTime);

			// cookieのmaxTimeを都度更新する
			let maxTimeStr = "maxTime=" + eTime + ";";
			document.cookie = maxTimeStr;  

			var eTimeShow = "経過時間(秒) " + eTime;
			console.log(eTimeShow);
			document.getElementById("elapsedTime").innerHTML = eTimeShow;

		return eTimeShow;
		}

		setInterval('showElapsedTime()', 1000);

	} else if (cnt = 2){
		let maxTime_ck = getCookieValue("maxTime");
		var eTimeShow = "経過時間(秒) " + maxTime_ck;
		console.log(eTimeShow);

		// 2回目の読込時は1回目の最大値で固定表示
		document.getElementById("elapsedTime").innerHTML = eTimeShow;
	}

</script>
<br>
<br>

</body>
</html>

参考サイト

[PHP] 07 Xdebugの導入 Command Line Debug Client

[M1 Mac, Big Sur 11.7.2, PHP 8.2.1, MySQL 8.0.31]

XdebugをVSCodeやPhpStormで使おうとするとセキュリティ対策のためかスクリプトでMySQLへの自動ログインができません。認証方式をmysql_native_passwordに変えてもダメでした。コンソールからphpコマンドで実行すると自動ログインは可能です。

調べてみるとXdebugはC/C++用デバッガ LLDBのようにターミナルでも使えることが分かりましたので、早速試しています。これで一気に突破口が開けたような気がします。

変数の確認さえできるのであればIDEは基本不要ですね。エディタで使う機能はフォルダ内検索置換と関数定義追跡、スクリプトの色付け位でしょうか。インタプリタやデバッガはエディタからは使わないです。

launch.jsonやsettings.jsonはなるべく触りたくないです。これまでVSCodeの設定で散々時間を浪費したので。OSやハードに近い立ち位置で開発したいです。介在するIDEやツール類は極力排除します。まあ個人開発ですから好き勝手にやってます。

ところで、Pythonにはpdbというデバッガがあるそうです。機会があれば使ってみたいです。

Xdebugのサイト

[PHP] 06 PhpStormの導入

[M1 Mac, Big Sur 11.7.2, PHP 8.2.1, MySQL 8.0.31]

MySQLアプリのプログレスバー実装であえなく詰んでしまいました。PHPのecho & printデバッグ、JavaScriptのvar_dumpデバッグとChromeのconsole.logだけでは変数の内容を十分に追跡できません。

これでまたJavaScriptから撤退すると3度目になってしまいます。今回は腹をくくって長期戦で攻略したいです。

少し前にVisual C++を学習した際はVisual Studioをある程度使い、言語仕様を把握した上で脱IDEを実行しました。PHP & JavaScript学習もこの作戦でいきます。

PHP専用のIDEとして有名なPhpStormを取りあえずインストールしました。

明日以降、少しずつ学んでいきます。

[PHP] 05 MySQLアプリ作成 全テーブル検索

[M1 Mac, Big Sur 11.7.2, PHP 8.2.1, MySQL 8.0.31]

データベース、カラム名、検索語を入力して全テーブル検索できるようにしました。

Pythonとは勝手が違うため、半日以上悪戦苦闘でした。

スクリプトの構成は以下の様になります。
1) mysql_main.php (前回記事と内容は同じ)
2) input.php
3) output.php

PHPはPythonのように配列を丸ごと出力することができないようです。Pythonの使いやすさを再認識しました。

これから実装したい機能は以下の通りです。
1)検索中断ボタン
2)プログレスバー(検索進行度表示)
3)検索結果CSVファイル化
4)検索時間測定

<html>
<?php
header('Cache-Control: no-store, no-cache, must-revalidate');


# MySQLにログイン
$conn = new mysqli('localhost', 'root');
if (!$conn){
	die("MySQL接続に失敗しました");
} else {
	// print 'MySQL接続に成功しました';
}

$dbs_query = $conn->query('show databases;');
$dbs = $dbs_query->fetch_all(MYSQLI_ASSOC);

$dbs_count = count($dbs);

?>
<head>
	<title>MySQL LPW</title>
</head>
<body>
データベース、カラム、検索語を入力してください
<form action="output.php" method="post">
	<table border="1">
		<tr>
			<td>DB</td>
			<td><input type="text" name="db"></td>
		</tr>
	</table>
	<tr>
	<?php
	$num = 0;
	foreach ($dbs as $db) {
		if ($num == 0){
			echo "DB : ";
		}
		echo implode($db);
		if ($num != $dbs_count - 1){
			echo ", ";
		}
		$num = $num + 1;
		
	}
	?>
	</tr>
	<table border="1">
		<tr>
			<td>COL</td>
			<td><input type="text" name="col"></td>
		</tr>

	</table>
	<table border="1">
		<tr>
			<td><input type="text" name="key"></td>
			<td colspan="2" align="center">
			<input type="submit" value="検索">
			</td>
		</tr>
	</table>
</form>
</body>
</html>
<html>
<?php
header('Cache-Control: no-store, no-cache, must-revalidate');
?>
<head>
    <title>MySQL LPW</title>
</head>
<body>
    <?php
    include_once'input.php';

    $db = $_POST['db'];
    printf("DB : %s\n", $db);
    $col = $_POST['col'];
    printf("COL : %s\n", $col);
    $key = $_POST['key'];
    printf("検索語 : %s\n", $key);

    # データベース選択
    $select_db = sprintf("use %s", $db);
    $select = $conn->query($select_db);

    # 全テーブル取得
    $tbls_query = $conn->query('show tables;');
    $tbls = $tbls_query->fetch_all(MYSQLI_ASSOC);

    $counts = 0;
    foreach ($tbls as $tbl) {
        $query = sprintf("SELECT * FROM `%s` WHERE `%s` LIKE '%%%s%%'", implode($tbl), $col, $key);
        $result = $conn->query($query);
        $rows = $result->fetch_all(MYSQLI_ASSOC);
        $count = count($rows);
        $counts = $counts + $count;
    }

    $conn->close();
    exec('mysql.server stop');

    ?>
    <table border="2">
    <tbody>
        <tr>
            <td>件数</td>
            <td width="180"><?php echo $counts; ?></td>
        </tr>
        </tbody>
    </table>
</body>
</html>