티스토리 툴바


My Programing/PHP2011/12/06 17:09
제가 올리는 팁이 항상 그렇듯이...
실력 좋은 분들은 패쓰해야 하는 허접한 팁입니다.
그점 감안하고 봐 주세요 ^^;;

일반적으로 php 프로그래밍은 길어봐야 수초 내에 끝나는 것이 대부분이다.
하지만, 메일을 보낸다거나 할 경우에는 소스 상단에 set_time_limit(0); 를 추가해서 보내는 경우가 있다.
물론 돈이 많거나, 실력이 뛰어난 프로그래머라면, 좋은 발송기를 사거나, 좋은 프로그램을 짜서 보내면 되지만,
나와 같이 허접한 실력의 프로그래머라면, php로 해결하는 수 밖에 없다 ^^;;;

보통의 경우... 대량 메일을 보내기 위해서는 서버단 설정을 먼저해야 한다.
요즘 나오는 센드메일은 멀티 큐를 지원하고 있고, 큐메일은 예전부터 멀티큐를 지원하기 때문에,
메일 서버 자체에서는 별로 설정할 일이 없다.
만일 메일 서버단의 설정이 궁금하다면, 멀티큐로 검색해 보면 많이 나올 것이다.

오늘 여기서 다루고자 하는 것은...
php 프로그램으로 대량 메일을 어떻게 보내야지만 좋을까 하는 것이다.
사장님의 압력에 몇번의 실패 끝에 알아내게 된 내용을 적어본다.

1. rcpt to를 이용한 방법

처음에 선임자가 짜 놓은 프로그램을 보니 smtp class를 이용해서 보내는 것이었다.
그런데 10만통을 5분에 쏜다고 사장님께서 말씀하시길래 이해가 안가서 소스를 뒤적여 봤더니...
헤더를 조작해서 보내는 것이었다 -.-;;
즉 메일의 rcpt to에다가 

aaa@hanmail.com,bbb@hanmail.com,ccc@hanmail.com...........zzz@naver.com

이런식으로 해 놓고, 헤더의 receive에는 그냥 '회원님' 하고 보내는 것이었다.

그러면 받는 사람한테는 rcpt to의 내용이 보이지도 않으면서, php 프로그램 상에서는 메일을 한통만 쏘는 효과가 있었던 것이었다 -.-;;
물론... 센드메일이나 큐메일은 rcpt to에 콤마로 이어놓은 메일 수 만큼 bacrground로 열심히 메일을 뿌리고 있겠지만... ^^;;

하지만 이 방법의 문제점은 header의 to 정보와 rcpt to의 to 정보가 불일치 하기때문에,
대부분의 메일서버가 스팸으로 분류한다는 단점이 있다 -.-;;
결국 보내봐야 소용없는 메일이 되고 만다 -.-;;
따라서 다른 방법을 찾아봐야만 했다.

2. smtp로 직접 접속해서 보내는 방법

smtp로 보내는 방법은 여러가지 설정화 헤더 정보 등을 임의로 입맛에 맞게끔 수정해서 보낼 수 있다.
하지만, 브라우져라면 한번에 한 창을, 실행파일이라면 한번에 한 프로세스만을 띠울 수 밖에 없다.
smtp 프로토콜을 이미 하나의 프로세스가 잡고 있기에, 다른 프로세스는 대기할 수 밖에 없기 때문이다.
따라서 php의 mail() 함수를 사용해서, smtp를 물고 있지 않더라도, MTU에 그냥 메일만 던져주고 빠지는 방식을 쓸 수 밖에 없었다.

3. 콘솔 상에서 보내는 방법

보통 브라우져로 메일을 보내게 되면, 브라우져=>아파치=>php=>MTU 의 단계를 거치게 된다.
또한 브라우져는 http 통신을 하게 되므로 지속적인 연결을 하면서 메일을 발송하는 것에는 조금 불안한 감이 있다.
따라서 php를 binary 버전으로 컴파일 한 후... 아파치와는 독립적으로, perl이나 sh 파일처럼 콘솔 상에서 직접 실행함으로서 조금은 안정적으로 보낼 수 있다.

* 문제점

본인의 경우에는 업체명을 쿼리한 후... 해당 업체에 소속된 회원에게 메일을 발송하는 작업이었다.
메일을 보내는 헤더정보와 메일 내용의 footer 부분에 해당 업체의 정보가 들어가야 하기에 루푸문 안의 쿼리는 어쩔 수 없었다.

업체의 정보와, 회원의 메일 정보를 가져오는 디비쿼리의 경우에는 메일을 보내는 동안 디비 커넥션을 계속 물고 있어야 하기에,
디비 클래스를 조금 수정하여, 해당 정보를 배열로 받고 난 후 접속을 끊어 버리는 형태로 만들었다.
하지만, 이 또한 문제점이 발생하였다.

ex)
01 $conn= new mysqlClass('호스트''디비병''유저명''패스워드');
02  
03 //회원정보 불러오기...
04 $sListQuery = "SELECT index, email, company_id FROM email_list WHERE ORDER BY company_id ASC LIMIT ".$argv[5].", ".$argv[6];
05 $aListRows = $conn->getData($sListQuery);
06  
07 if(is_array($aListRows)) {
08     foreach($aListRows AS $key=>$value) {
09         //업체 정보가 틀려 졌다면...
10         if($aListRows[$key]['company_id'] != $aListRows[$key-1]['company_id']) {
11             //업체 정보 쿼리
12             $sComQuery = "
13                 SELECT
14                     A.ID, A.Name, A.Email,
15                     B.Company, B.Addr, B.Phone, B.Fax, B.Homepage, B.NickName
16                 FROM MemberOfCompany AS B
17                 LEFT JOIN Member AS A ON A.id=B.id
18                 WHERE A.ID='".$aListRows[$key]['company_id']."'";
19             $aComRows = $conn->getData($sComQuery, 1);
20  
21             //메일 header 생성
22  
23             //메일 body 생성
24         }
25  
26         if(($bResult = mail($aListRows[$key]['email'], $argv[4], $body, $headers)) == 1) {
27             echo "Send OK, ".$aListRows[$key]['index'].",${NickName}사무실, Sucess Count - $i, Sucess Email -".$aListRows[$key]['email']."\n";
28             $i++;
29         }
30         else {
31             echo "Send Error : ".$aListRows[$key]['index'].", ${NickName}사무실, Failure Email - ".$aListRows[$key]['email']."\n";
32             exec("echo '".$aComRows['ID'].",".$aListRows[$key]['email']."' >> ".date('Ymd')."_Company.log");    //로그기록...
33         }
34     }
35 }



위와 같은 소스로 메일을 발송하다보면... 중간에... 다음과 같은 에러가 발생한다

1 ::2013Lost connection to MySQL server during query2013


커넥션이 끊어졌다는 것이다. -.-;;
바로 업체 정보를 가져오는 $sComQuery를 실행할 수 없어서 에러가 난 것이다.
그러면서 그 이후의 메일 발송은 바로 stop이 되는 난감한 상태가 발생한 것이다.
그렇다고 해서 connection 타임을 mysql 설정 단에서 늘려주면, 일반 웹서비스로 인해 디비커넥션이 full 되서 디비서버 자체가 죽을 우려가 있었기 때문이다.

조금의 고민 끝에... 커넥션이 끊어 졌으면 pconnect를 써야 겠다 라는 생각에 클래스를 pconnect로 접속하도록 바꿨지만, 이 역시 마찬가지 결과를 가져왔다.

그래서 결국에는 '커넥션 끊어지면, 다시 커넥션 하면 되지 뭐 -.-;;' 라는 생각에...
is_resource 함수를 사용하기로 했다 ^^;;

******* 바꾼 소스... ************
01 if(!is_resource($conn)) $conn= new mysqlClass('호스트''디비병''유저명''패스워드');
02  
03 //업체 정보 쿼리
04 $sComQuery = "
05     SELECT
06         A.ID, A.Name, A.Email,
07         B.Company, B.Addr, B.Phone, B.Fax, B.Homepage, B.NickName
08     FROM MemberOfCompany AS B
09     LEFT JOIN Member AS A ON A.id=B.id
10     WHERE A.ID='".$aListRows[$key]['company_id']."'";
11 $aComRows = $conn->getData($sComQuery, 1);



그런데... 다시 생각해 보니 $conn은 원래부터가 resource가 아니였던 것이다. -.-;;
메뉴얼에도 잘 나와 있다시피...

1 $db_link = @mysql_connect('localhost''mysql_user''mysql_pass');


처럼 접속 했을 때 $db_link가 resource인 것이지, new를 통해 생성한 객체는 object 였던 것이다 -.-;;

그래서 결국에는 class에 메쏘드 하나를 추가하기로 했다. -.-;;


1 function checkResource() {
2     if(is_resource($this->_CONN)) {
3         return true;
4     }
5     else {
6         return false;
7     }
8 }


그리고 나서 위에 소스도 다음과 같이 수정했다.

******* 최종 소스... *********
01 if(!$conn->checkResource()) $conn= new mysqlClass('호스트''디비병''유저명''패스워드');
02  
03 //업체 정보 쿼리
04 $sComQuery = "
05     SELECT
06         A.ID, A.Name, A.Email,
07         B.Company, B.Addr, B.Phone, B.Fax, B.Homepage, B.NickName
08     FROM MemberOfCompany AS B
09     LEFT JOIN Member AS A ON A.id=B.id
10     WHERE A.ID='".$aListRows[$key]['company_id']."'";
11 $aComRows = $conn->getData($sComQuery, 1);



아직까지는 잘 돌아가고 있으며, 메일 10만통을 보내는 데 1시간 정도 걸린다.(물론 콘솔을 4개 띄워서 동시에 4개의 프로세스로 돌린 경우이다 ^^;;)
php로 한시간 정도 에러 안나고 돌아가면... 성공한 것 아닌가? ㅋㅋㅋ

어째든... 아무런 에러 없이 계속 돌아가 주기만을 바랄뿐이다 ^^


출처: http://www.4te.co.kr 
Posted by nkdk
My Programing/PHP2011/12/05 13:41
<?php
function createThumbs( $pathToImages, $pathToThumbs, $thumbWidth ) 
{
  // open the directory
  $dir = opendir( $pathToImages );

  // loop through it, looking for any/all JPG files:
  while (false !== ($fname = readdir( $dir ))) {
    // parse path for the extension
    $info = pathinfo($pathToImages . $fname);
    // continue only if this is a JPEG image
    if ( strtolower($info['extension']) == 'jpg' ) 
    {
      echo "Creating thumbnail for {$fname} <br />";

      // load image and get image size
      $img = imagecreatefromjpeg( "{$pathToImages}{$fname}" );
      $width = imagesx( $img );
      $height = imagesy( $img );

      // calculate thumbnail size
      $new_width = $thumbWidth;
      $new_height = floor( $height * ( $thumbWidth / $width ) );

      // create a new temporary image
      $tmp_img = imagecreatetruecolor( $new_width, $new_height );

      // copy and resize old image into new image 
      imagecopyresized( $tmp_img, $img, 0, 0, 0, 0, $new_width, $new_height, $width, $height );

      // save thumbnail into a file
      imagejpeg( $tmp_img, "{$pathToThumbs}{$fname}" );
    }
  }
  // close the directory
  closedir( $dir );
}
// call createThumb function and pass to it as parameters the path 
// to the directory that contains images, the path to the directory
// in which thumbnails will be placed and the thumbnail's width. 
// We are assuming that the path will be a relative path working 
// both in the filesystem, and through the web for links

createThumbs("upload/","upload/thumbs/",100);
?>
Posted by nkdk
My Programing/PHP2010/10/29 23:24
오랜만에 포스팅 하는군요. ㅎ

요즘은 일본 블로그에서만 놀고 있다 보니까 이 곳에서의 블로깅이 아주 소홀해 졌네요.

오랜만에 오늘 하루 간단하게 설정 한 내용을 블로깅 해 볼까 합니다.

1. 우분투 10.10 서버 설치
거의 완벽한 국제화 설정이 되어 있어서 너무 좋더군요.
더군다나 10.10 느낌이 좋더라고요.

일단 apm 을 기본적으로 설치 했는데 php가 안 먹길래 설정을 고쳐보았습니다.

자 시작..

일단 간단히
/var/apache2/apache2.conf를 열어서 아무곳에다가

  ServerName w.snssuite.com 

이와 같이 원하는 서버 이름을 추가합니다.
그 다음으로는 
/etc/apache2/mods-enabled/php5.conf 를 엽니다 여긴 파일 매칭해서 php를 먹히게 할 것인가에 대한 설정인데요.

저는
    <FilesMatch "\.html$">
        SetHandler application/x-httpd-php
    </FilesMatch>

요것을 추가했습니다 :)

자 그럼 apachectl 을 이용해서
  apachectl -t 
이걸로 syntax 체크를 실시 해보고요 ok 이시죠?

자 그럼.. 
 apachectl graceful
그럼 아파치가 재 기동 됩니다.

잘 되는지에 확인은 /var/www/index.html 을 열어서 <?php phpinfo(); ?>

잘 될겁니다.

자 다음으로 외장 하드를 잡아 봤습니다. usb 를 연결된 하드고요.

  /sbin/mount.ntfs-3g /dev/sdb1 /media/usb -o rw

이걸로 하면 되고요 /dev/sdb1 혹은 sda1, sdc1 이것중에 하나 일겁니다. 꼽는 포트에 따라 틀려요 :)

자 이번엔 삼바:)
혹시 삼바가 안 깔려 있다면
  sudo apt-get samba

하시면 쭉 설치 됨. 데비안 계열이기 때문에 apt-get 이지요 :)

이렇게 설치 되시면

  smbpassword -a 추가하고자하는id(존재하는 id로 하는게 좋아요) 

이렇게 하시면 삼바 사용자도 추가 완료 :)

/etc/samba/smb.conf 요 부분 설정은 설명이 써 있으니까 패스

mysql 이라던지에 셋팅도 패스

기본적인 프로그램을 설치 했어요.

sudo apt-get vi
sudo apt-get git
sudo apt-get openssl
sudo apt-get maven2
sudo apt-get screen

일단 이 정도만 설치 해 놓으면 웹 개발 환경으로는 퍼펙트 하네요 :)
기본적으로 필요하신 것 있으면서 설치 하시면 됩니다.

일단 서버적으로는 잘 정리되어 있어서 좋네요.

다음 기회에 좀 더 재미난 걸로 나옵니다. ㅎ
Posted by nkdk
My Programing/PHP2010/04/09 12:59
http://mygony.com/archives/1115

Posted by nkdk
My Programing/PHP2010/03/30 14:54

역시 세상에는 똑똑한 사람이 참 많은거 같아요. RDB에서 계층구조 표현 방식이 참 애매하고 복잡하기만 했는데 이런 방식을 쓰니 아주 간단히 가능하게 되버렸네요. ^^


さて今回は、「RDBで階層構造を扱うには?」です。
あるサイトを構築中に階層構造をもったカテゴリ構造にすることになり、DBでどのよう に扱うか悩みました。
DBはMySQLを採用していたので、この時点でぱっと頭に浮かんだ選択肢は以下のようなものでした。

  1. XML-DBを利用する
  2. 親カテゴリレコードのプライマリIDを子カテゴリレコードに持たせる
  3. 親を含めた『絶対パス』を名称として扱い、取り出した後にパース
  4. ファイルシステムに同様のディレクトリ構造を作り、毎回パースする

(1)のXMLDBはオープンソースのeXistやXindice、Yggdrasillなど様々な選択肢がありましたが、カテゴリのみの利用な割 にメンテナンスコストが高すぎるので見送りました。
(2)の親ID格納の実装は容易ですが、取り出し時のクエリが何回も行われるなど、負荷がかかるうえ、何よりエレガントさがありません。
(3)は一旦採用しかけたのですが、取り出しロジックの複雑さが増すので、できればやりたくありません。
(4)は柔軟な対応や検索など相当大変な気がしたので見送り。
すべての案がボツになったところで、いいアイデアはないかとグーグル先生に聞いてみたところ、以下のページが目からウロコだったのでさっそく採用してみま した。

Storing Hierarchical Data in a Database
http://www.sitepoint.com/article/hierarchical-data-database/

英語ですが、PHPとMySQLのソースも掲載されているので、それほど難しくないと思います。
おおざっぱに説明すると、ルートを頂点として各ノードの左右に順にIDを振り、このIDを利用して範囲検索することで、柔軟な取得を可能にするというもの でした。
例となる構造をここに示します。

                     +--------+
1|ジョージ|20
+--------+
|
+-------+-----------+
| |
+----------+ +------+
2|ジョナサン|15 16|ディオ|19
+----------+ +------+
| |
+-----------+ +--------+
3|ジョージ2世|14 17|ジョルノ|18
+-----------+ +--------+
|
+--------+
4|ジョセフ|13
+--------+
|
+------+-------+
| |
+------+ +--------+
5|ホリィ|10 11|東方仗助|12
+------+ +--------+
|
+----------+
6|空条承太郎|9
+----------+
|
+--------+
7|空条徐倫|8
+--------+

ルートにあたるジョージの左IDが1から始まり、左から順に子孫に番号を振りつつ、 末端の空条徐倫までたどり着くと右IDを振りつつ戻ります。
同様に、兄弟のディオの系列にも番号を振り、最終的にジョージへ戻ります。
テーブルにすると以下のようになります。

CREATE TABLE family (
"id" INTEGER NOT NULL AUTO_INCREMENT,
"left_id" INTEGER NOT NULL,
"right_id" INTEGER NOT NULL,
"name" VARCHAR(20) NOT NULL,
"gender" CAHR(1) DEFAULT "M" NOT NULL,
"stand" VARCHAR(100)
PRIMARY KEY ("id")
);

+--+-------+--------+-----------+------+--------------------------+
|id|left_id|right_id| name |gender| stand |
+--+-------+--------+-----------+------+--------------------------+
| 1| 1| 20|ジョージ | M | |
| 2| 2| 15|ジョナサン | M | |
| 3| 3| 14|ジョージ2世| M | |
| 4| 4| 13|ジョセフ | M |ハーミット・パープル |
| 5| 5| 10|ホリィ | F | |
| 6| 6| 9|空条承太郎 | M |スター・プラチナ |
| 7| 7| 8|空条徐倫 | F |ストーン・フリー |
| 8| 11| 12|東方仗助 | M |クレイジー・ダイヤモンド |
| 9| 16| 19|ディオ | M |ザ・ワールド |
|10| 17| 18|ジョルノ | M |ゴールド・エクスペリエンス|
+--+-------+--------+-----------+------+--------------------------+

親を含めて子孫を取得する場合、左右IDの数字を指定してやれば取得できます。
例としてジョセフとその子孫を取得する場合は以下のようになります。

 SELECT left_id, right_id, name
FROM family
WHERE left_id BETWEEN 4 AND 13
ORDER BY left_id ASC

+-------+--------+-----------+
|left_id|right_id| name |
+-------+--------+-----------+
| 4| 13|ジョセフ |
| 5| 10|ホリィ |
| 6| 9|空条承太郎 |
| 7| 8|空条徐倫 |
| 11| 12|東方仗助 |
+-------+--------+-----------+

子孫から親をたどる場合、左IDが子ノードの左ID以下、右IDが子ノードの右以上のIDを探します。
例として東方仗助の親を探す場合は以下のようになります。

 SELECT left_id, right_id, name
FROM family
WHERE left_id < 11
AND right_id > 12
ORDER BY left_id ASC

+-------+--------+-----------+
|left_id|right_id| name |
+-------+--------+-----------+
| 1| 20|ジョージ |
| 2| 15|ジョナサン |
| 3| 14|ジョージ2世|
| 4| 13|ジョセフ |
| 11| 12|東方仗助 |
+-------+--------+-----------+

あるノードから数えた子孫の数は、以下の計算式で算出します。

(ノード右ID - ノード左ID - 1) / 2

ジョナサンを例にすると、

(15 - 2 - 1) / 2 = 6

となり、子孫は6人となります。

また、新たにノードを増やす場合、あらかじめ左右のIDをずらしておきます。
たとえば空条徐倫に娘ができた場合は以下のようになります。

 UPDATE family SET right_id = right_id + 2 WHERE right_id > 7 ORDER BY right_id DESC;
UPDATE family SET left_id = left_id + 2 WHERE left_id > 7 ORDER BY left_id DESC;
INSERT INTO family (left_id, right_id, name, gender) VALUES(8, 9, '徐倫の娘');

これを実行すると以下のようなデータになります。


+--+-------+--------+-----------+------+--------------------------+
|id|left_id|right_id| name |gender| stand |
+--+-------+--------+-----------+------+--------------------------+
| 1| 1| 22|ジョージ | M | |
| 2| 2| 17|ジョナサン | M | |
| 3| 3| 16|ジョージ2世| M | |
| 4| 4| 15|ジョセフ | M |ハーミット・パープル |
| 5| 5| 12|ホリィ | F | |
| 6| 6| 11|空条承太郎 | M |スター・プラチナ |
| 7| 7| 10|空条徐倫 | F |ストーン・フリー |
| 8| 13| 14|東方仗助 | M |クレイジー・ダイヤモンド |
| 9| 18| 21|ディオ | M |ザ・ワールド |
|10| 19| 20|ジョルノ | M |ゴールド・エクスペリエンス|
|11| 8| 9|徐倫の娘 | F | |
+--+-------+--------+-----------+------+--------------------------+

-- 徐倫の娘の親をたどる
SELECT left_id, right_id, name
FROM family
WHERE left_id < 8
AND right_id > 9
ORDER BY left_id ASC
+-------+--------+-----------+
|left_id|right_id| name |
+-------+--------+-----------+
| 1| 22|ジョージ |
| 2| 17|ジョナサン |
| 3| 16|ジョージ2世|
| 4| 15|ジョセフ |
| 5| 12|ホリィ |
| 6| 11|空条承太郎 |
| 7| 10|空条徐倫 |
| 8| 9|徐倫の娘 |
+-------+--------+-----------+

この更新方法にはパフォーマンス上の様々な意見があるので、 このモデルをさらに推し進めた方法も存在するようです。
今回はleft_idとright_idに整数値を利用しましたが、ノード追加時に更新が発生するのを防ぐため小数値を利用する方法もあります。
具体的には左IDを8,右IDを9ではなく、左IDを7.3、右IDを7.6にすることで、他レコードへの更新を防ぎINSERT文1度で済むようにす る、という方法です。
この方法であれば、親子関係は維持したまま、だいぶパフォーマンスでは有利になります。
私の場合はそれほど頻繁に更新しないのと、小数だと全体の見渡しがよくなくなると考えたため、この方法は採用しませんでした。

私は上記サイトでこの設計を知ったのですが、後になってWEB+DB PRESSを見直した所 同様の記事を見つました。(Vol49, 50の『SQLアタマアカデミー』著:ミック) 英語が苦手な方は是非こちらをお勧めします。
また筆者の方のWEBサイトにはより詳細に網羅的に記載があります。

SQLで木と階層構造のデータを扱う(1)―― 入れ子集合モデル
http://www.geocities.jp/mickindex/database/db_tree_ns.html

概念自体は古くからあるようなのでご存知の方は多いと思いますが、 今回の自分のように目からウロコがボロボロ落ちる方がいるかもしれないと思い エントリーを書き起こしました。
よいSQLライフを!

[23:52] 「徐倫の娘」(ノード追加) についての実行結果と、小数を利用した方法を追記しました。

Posted by nkdk
My Programing/PHP2010/03/25 22:20
간단하게 function으로 만들어 봤다

  // メールを送信
  function sendMail($to, $subject, $body, $from_email, $from_name) {
    // カレントの言語を日本語に設定する
    mb_language("ja");
    // 内部文字エンコードを設定する
    mb_internal_encoding("UTF-8");

    $headers  = "MIME-Version: 1.0 \n" ;
    $headers .= "From: " .      "".mb_encode_mimeheader(mb_convert_encoding($from_name,"ISO-2022-JP","AUTO")) ."" .  "<".$from_email."> \n";
    $headers .= "Reply-To: " .  "".mb_encode_mimeheader(mb_convert_encoding($from_name,"ISO-2022-JP","AUTO")) ."" .  "<".$from_email."> \n";
    $headers .= "Content-Type: text/plain;charset=ISO-2022-JP \n";
     
    $body = mb_convert_encoding($body, "ISO-2022-JP","AUTO");
     
    $sendmail_params  = "-f$from_email";
     
    $result = mb_send_mail($to, $subject, $body, $headers, $sendmail_params);

    return $result;
  }

뭐 ^^
Posted by nkdk
My Programing/PHP2010/01/20 21:17

http://kr.blog.yahoo.com/hslee49/1337026


symfony 에서 데이터는 객체를 통해서 접근이 된다. 만약 관계형 모델과 SQL 을 통한 데이터 수정 및 선택에익숙하다면, 객체 모델 방법은 복잡해 보일 수 있다. 그러나 일단 한 번 데이터 접근을 위한 객체지향의 힘에 맛을 들이게된다면, 이 방법을 더욱 좋아하게 될 것이다. 그러나 먼저 같은 용어에 대해서 확실히 할 필요가 있겠다. 관계형 그리고 객체데이터 모델은 비슷한 개념을 사용하지만 각자의 명칭을 가지고 있다.

Rerational    Object-Oriented
Table              Class
Row,record      Object
Field, column   Property

 ** 컬럼 값 가져오기 **
symfony가 모델을 형성할 때, 하나의 기본 객체 클래스를 schema.yml 파일에 정의된 각 테이블 마다 생성을 한다. 이들 각각의클래스들은 기본적으로 형성자 new, 접근자 getXXX() 그리고 컬럼정의에 의거한 변이형성자 setXXX() 를 제공한다.이 메쏘드들은 객체를 생성하고 객체의 자산에 대한 접근을 제공한다.

* 생성된 객체 클래스 메쏘드들 *
$article = new Article();
$article->setTitle('My first article');
$article->setContent('This is my very first article.\n Hope you enjoy it!');
 
$title   = $article->getTitle();
$content = $article->getContent();

여러개의 필드값을 설정하기 위해서 fromArray() 메쏘드를 사용할 수 있으며 이는 각 객체 클래스를 위해서 생성이 되어 있다.

$article->fromArray(array(
  'title'   => 'My first article',
  'content' => 'This is my very first article.\n Hope you enjoy it!',
));


 ** 관련된 기록들을 가져오기 **
blog_comment테이블의 article_id 컬림은 함축적으로 blog_article 에 대한 외부 키임을 정의하고 있다. 각 comment 는하나의 article 에 연관되어 있고 하나의 article 은 다수의 comment 를 가질 수 있다. 생성된 클래스들은다섯개의 메소드를 포함하고 있으며 이들은 객체지향 방법으로 이들 관계를 해석하게 한다 :
    * $comment->getArticle(): Article 객체 얻기
    * $comment->getArticleId(): 관련된 Article 객체의 ID 얻기
    * $comment->setArticle($article): 관련된 Article 객체 규정
    * $comment->setArticleId($id): ID 로 부터 관련된 Article 객체 규정하기
    * $article->getComments(): 관련된 Comment 객체 얻기

getArticleId(),setArticleId() 메소드들은, article_id 컬럼을 일반 컬럼으로 여길 수 있지만 편하게 관계를 설정하는 것으로생각할 수 있게 한다. 객체지향적인 접근의 장점은 다른 세 메소드안에 더 명확해 진다.

* 외부키는 틀별한 세터로 해석이 된다 *
$comment = new Comment();
$comment->setAuthor('Steve');
$comment->setContent('Gee, dude, you rock: best article ever!');
 
// Attach this comment to the previous $article object
$comment->setArticle($article);
 
// Alternative syntax
// Onl y makes sense if the object is already saved in the database
$comment->setArticleId($article->getId());

생성된 getter 들을 어떻게 사용하는지 보자. 연쇄 메소드 호출의 예제는 다음과 같다.

// Many to one  relationship
echo $comment->getArticle()->getTitle();
 => My first article
echo $comment->getArticle()->getContent();
 => This is my very first article.
    Hope you enjoy it!
 
// One  to many relationship
$comments = $article->getComments();

getArticle()메쏘드는 Article 클래스의 객체를 반환하며, 이 객체는 getTitle() 접근자를 사용할 수 있게 해준다. 이는$comment->getArticleId() 호출을 시작으로 하는 두세 라인의 코드를 사용하는 결합하는 것보다 훨씬 나을것이다.  $comment 변수는 Comment 클래스 객체의 배열을 포함하고 있다. 첫 배열의 값을 $comment[0] 을이용하거나 foreach 를 이용한 반복을 사용하여 출력할 수 있다.

 ** 데이터 저장 및 삭제 **
new생성자 호출을 통해 새로운 객체를 생성하지만 blog_article 테이블 에서의 실제 기록을 하는 것은 아니다. 객체를수정하는 것은 데이터베이스에 영항을 주는 것 또한 아니다. 데이터베이스에 데이터를 저장하기 위해서는 save() 메쏘드를호출해야만 한다.

 $article->save();

ORM 은 객체들 사이의 관계를 판단하기에충분히 현명하여 $article 객체를 저장함과 동시에 관련된 $comment 객체도 저장한다. 또한 저장된 객체가 데이터베이스내에 짝을 이루는 테이블이 존재하는지 알고 있기때문에 save() 호출은 때때로 SQL 의 INSERT 로 해석이 될 수도있으며, 때로는 UPDATE 로 해석될 수 도 있다. Primary key 는 자동적으로 save() 메쏘드에 의해서 설정되고저장된 후에는 $article->getId() 를 이용하여 새 primary key 를 얻어올 수 있다.
isNew()를 이용하여 객체가 새로운 것인지 확인할 수 있으며 isModified() 를 이용하여 객체가 수정되어서 저장해야 할 필요가있는지 확인할 수 있다. delete() 메쏘드를 이용해서는 원하는 comment 들을 삭제할 수 있다.

foreach ($article->getComments() as $comment)
{
  $comment->delete();
}

때로는 delete() 메쏘드 호출후에도 객체는 request 가 끝날때 까지 유효할 수 있다. 데이터베이스에서 삭제되었는지 확인하기 위해서 isDeleted() 메쏘드를 사용하면 된다.

 ** Primary key 를 사용하여 데이터 가져오기 **
 특별한 데이터에 대해서 primary key 를 알고 있다면 동료 클래스의 retrieveByPk() 메쏘드를 이용하라.
$article = ArticlePeer::retrieveByPk(7);

schema.yml파일은 id 를 blog_article 테이블의 primary key 로서 정의하며, 이 구문이 실제로 id 값 7 을 가진article 을 반환하게 된다. Primary key 를 사용하면 오로지 하나의 레코드 값만이 반환이 된다.
어떤경우에 primary key 는 하나의 컬럼 이상을 포함 할 수 있다. 이러한 경우네 retrieveByPk() 메쏘드는 다중매개변수값을 허용한다. 또한 retrieveByPKs() 메쏘드를 통해 primary key 값들에 근거한 다중 객체들을 선택할수 도 있다.

 ** 조건에 의거한 레코드 가져오기 **
하나 이상의 레코드를 가져오기를 원한다면 해당하는 동료 클래스의 doSelect() 메쏘드를 호출 해야 한다. 예를들어 ArTicle 클래서의 객체를 가져와야 한다면 ArticlePeer::doSelect() 를 호출한다.
doSelect()의 첫 매개변수는 Criteria 클래스의 객체인데 이 객체는 간단히 데이터베이스 추상화를 위해서 SQL 을 가지고 있지는 않는질의 정의 클래스라 할 수 있다. 빈 Criteria 는 클래스의 객체들을 반환한다. 예를 들어 모든 article 들을가져오려면...

$c = new Criteria();
$articles = ArticlePeer::doSelect($c);
 
// Will result in the following SQL query
SELECT blog_article.ID, blog_article.TITLE, blog_article.CONTENT,
       blog_article.CREATED_AT
FROM   blog_article;

doSelect()를 호출하는 것은 단순 SQL 질의 보다 더욱 강력하다. 첫째로 SQL 이 선택한 DBMS 를 위해 최적화 되어 있다. 둘째로어떤 값이 Criteria 에 전달이 되더라도 SQL 에 전달되기 이전에 SQL injection 위험에 대비하여 값이안전화되어 있다. 셋째로 결과 set 보다는 객체들의 배열을 반환한다. ORM 은 자동적으로 데이터베이스 결과 값에 근거한객체를 생성한다. 이것을 hydrating 이라 부른다.


좀더 복잡한 객체 선택을 위해 WHERE,ORDER BY, GROUP BY 그리고 다른 SQL 구문들을 대신할 수 있는 것이 필요하다. Criteria 객체는 이러한모든 조건들에 대한 메쏘드와 매개변수들을 가지고 있다. 예를 들어 steve 에 의해 작성된 모든 comment 들을, 날짜순으로 가져와야 한다면 Criteria 를 다음과 같이 구성한다.

$c = new Criteria();
$c->add(CommentPeer::AUTHOR, 'Steve');
$c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
$comments = CommentPeer::doSelect($c);
 
// Will result in the following SQL query
SELECT blog_comment.ARTICLE_ID, blog_comment.AUTHOR, blog_comment.CONTENT,
       blog_comment.CREATED_AT
FROM   blog_comment
WHERE  blog_comment.author = 'Steve'
ORDER BY blog_comment.CREATED_AT ASC;

add()메쏘드에 매개변수로 전달된 클래스 상수값들은 자산의 이름을 나타낸다. 위에서 보듯이 각 컬럼들의 대문자로 이루어졌음을 알 수있다. 예를 들어 blog_article 테이블의 컬럼 content 를 나타태기 위해서 ArticlePeer::CONTENT와 같은 클래스 상수를 언급했다.

blog_comment.AUTHOR 대신에 CommentPeer::AUTHOR 를사용하는 이유는 그것이 SQL 질의의 출력이 되기 때문이다. author 필드의 name을 contributor로 변경한다고가정해보자. 이때 blog_comment.AUTHOR 를 사용했다면 매번 모델(model)을 호출 할 때마다 이 부분을 바꿔줘야할 것이다. 반면에 CommentPeer:AUTHOR 를 사용하면 간단히 schema.yml 파일의 한 컬럼만 바꿔주고모델(model)을 다시 형성해주면 된다.

* SQL 구문과 Criteria 객체 구문 비교 *
<pre>
SQL                                   Criteria
WHERE column = value          ->add(column, value);
WHERE column <> value        ->add(column, value, Criteria::NOT_EQUAL);
Other Comparison Operators  
> , <                                    Criteria::GREATER_THAN, Criteria::LESS_THAN
>=, <=                                  Criteria::GREATER_EQUAL, Criteria::LESS_EQUAL
IS NULL, IS NOT NULL           Criteria::ISNULL, Criteria::ISNOTNULL
LIKE, ILIKE                           Criteria::LIKE, Criteria::ILIKE
IN, NOT IN                           Criteria::IN, Criteria::NOT_IN
Other SQL Keywords  
ORDER BY column ASC         ->addAscendingOrderByColumn(column);
ORDER BY column DESC       ->addDescendingOrderByColumn(column);
LIMIT limit                            ->setLimit(limit)
OFFSET offset                       ->setOffset(offset)
FROM table1, table2
WHERE table1.col1 = table2.col2  ->addJoin(col1, col2)
FROM table1 LEFT JOIN table2 ON
table1.col1 = table2.col2              ->addJoin(col1, col2, Criteria::LEFT_JOIN)
FROM table1 RIGHT JOIN table2 ON
table1.col1 = table2.col2         ->addJoin(col1, col2, Criteria::RIGHT_JOIN)
</pre>

형성된 클래스에서 유효한 메쏘드(method)들을 이해하고 찾아내는 가장 좋은 방법은 lib/model/om/ 폴더에 있는기본(base) 파일들을 보는 것이다. 메쏘드 이름들은 다소 명확하다, 그러나 그위에 좀더 추가적인 코멘트를 하는것이 필요하면config/propel.ini 파일의 propel.builder.addComments 매개변수를 true 로 설정하고 모델을재생성 하면 된다.

다수의 조건을 가진 Criteria 의 다른 예를 보자. "enjoy"라는 단어를 포함하는 모든 기사에 대해 Steve가 추가한 의견들을 날짜순으로 가져오는 것이다.

$c = new Criteria();
$c->add(CommentPeer::AUTHOR, 'Steve');
$c->addJoin(CommentPeer::ARTICLE_ID, ArticlePeer::ID);
$c->add(ArticlePeer::CONTENT, '%enjoy%', Criteria::LIKE);
$c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
$comments = CommentPeer::doSelect($c);
 
// Will result in the following SQL query
SELECT blog_comment.ID, blog_comment.ARTICLE_ID, blog_comment.AUTHOR,
       blog_comment.CONTENT, blog_comment.CREATED_AT
FROM   blog_comment, blog_article
WHERE  blog_comment.AUTHOR = 'Steve'
       AND blog_article.CONTENT LIKE '%enjoy%'
       AND blog_comment.ARTICLE_ID = blog_article.ID
ORDER BY blog_comment.CREATED_AT ASC

SQL이 매우 복잡한 질의를 만들 수 있도록 해주는 간단한 언어인것 처럼, Criteria 객체도 복잡한 수준의 조건을 다룰 수있다. 그러나 많은 개발자들이 조건을 객체지향의 논리로 해석하기 이전에 먼저 SQL을 가지고 생각하기 때문에 Criteria객체는 아마도 처음에는 이해하기 어려울 수 있겠다. 이해하기 가장 쉬운 방법은 예제들로 부터 배우는 것이겠다. Symfony 의홈페이지는 여러각도로 여러분을 개발시켜줄 Criteria 생성의 예제로 가득하다. doSelect() 메쏘드이외에 모든 동료클래스는 doCount() 메쏘드를 를 가지고 있는데 이는 매개변수로 전달된 기준을 만족시키는 데이터의 숫자를 단순히 세어정수로 반환해준다. 객체를 반환하는 경우는 없기 때문에 doCount() 는 doSelect() 보다 훨씬 빠르다. 
동료 클래스는 또한 doDelete(), doInsert(), doUpdate() 메쏘드들도 제공하며, Criteria 를매개변수를 받아들인다. 이 메쏘드들은 DELETE, INSERT, UPDATE 질의를 데이터베이스에 보낼 수 있게 해준다. 이Propel 메쏘드들에 대한 더 자세한 내용은 모델안에 생성되어있는 동료 클래스들을 확인하면 된다.
마지막으로 첫반환되는 객체만을 원한다면 doSelect() 대신에 doSelectOne() 를 사용하면 된다. 아마도 Criteria 가 단하나의 결과만을 반환한다는 것을 알고 있을때일 것이며, 장점은 이 메쏘드가 객체의 배열보다는 하나의 객체만 반환한 다는 것이다.

doSelect() 질의가 많은 수의 결과를 반환할 때, 단지 응답에는 그 일부만 보여주고 싶어할 수 있다.Symfony 는 sfPropelPager 라 불리는 페이징기능을 제공하는 클래스를 제공하는데, 이는 결과를 자동으로페이징한다. 더 자세한것은 http://www.symfony-project.org/cookbook/1_0/en/pager 을 참고하자.

** 원형 그대로의 SQL 질의 사용하기 **
때로는 객체를 가져오기 보다는 데이터베이스에 의해 계산되어진 단순하게 합성된 결과들만을 원할 수 있다. 예를 들어 가장 최신의모든 기사들이 생성된 날짜를 얻고자 할 때, 모든 기사들을 가져와서 배열상에서 반복작업을 하는 것은 그리 효과적이라 할 수없다.  아마도 데이터베이스에 객체 보다는 단지 원하는 결과만을 돌려줄것을 요청하고 싶어할 것이다.
반면에 데이터베이스관리를 직접적으로 하기위해서 PHP 명령을 호출하고 싶지 않을 것이다, 왜냐하면 그렇게 하면 데이터베이스 추상화의 장점을잃어버리게 될 것이기 때문이다. 이것은 ORM 인 Propel 을 우회해 간다는 것을 의미하지만 데이터베이스 추상인 Croel을 피해가는 것은 아니다.
데이터베이스 질의를 Croel 을 사용해서 하려면 다음을 따라야 한다 :
 1. 데이터베이스 연결
 2. 질의 생성
 3. 선언문 생성
 4. 선언문 실행으로 부터의 결과 셋 반복
이것이 무슨말인지 잘 모르겠다면 아래의 예제가 좀더 분명하게 해줄 것이다.

 * Croel 을 이용한 SQL 질의
$connection = Propel::getConnection();
$query = 'SELECT MAX(%s) AS max FROM %s';
$query = sprintf($query, ArticlePeer::CREATED_AT, ArticlePeer::TABLE_NAME);
$statement = $connection->prepareStatement($query);
$resultset = $statement->executeQuery();
$resultset->next();
$max = $resultset->getInt('max');

Propel selection 처럼, Croel 질의들또한 처음 사용시에는 어렵다. 다시 말하지만 기존의 응용프로그램 예제로 부터, 또 지도서로 부터 바른사용법을 보게 될 것이다.

만약 이 프로세스와 데이터베이스의 직접 접근하는 방법을 지나쳐가고 싶다면 Croel 에 의해 제공되는 보안과 추상화를 잃어버리는부담을 갖게 될 것이다. Croel 방법으로 하는 것이 더 길지만 퍼포먼스와 이동성 그리고 보안에 대한 보장을 위한 훌륭한연습이 될 것이다. 이는 특히 질의들이 신뢰할 수 없는 곳으로 부터의 매개변수를 포함하고 있을때 더더욱 확실해 진다.Croel은 필요한 모든 데이터 가공과 데이터베이스의 안전화를 수행한다. 직접적인 데이터베이스 접근은 SQL-injection공격의 위험에 바로 노출되게 하기도 한다.

** 특별 날짜 컬럼 사용하기 **
 일반적으로 테이블이 created_at 컬럼을 가지고 있을때, 이것은 해당 기록이 생성된 날의 시간기록을 저정하는데 사용된다.이와 같이 updated_at 컬럼도 동일하게 적용이 되는데, 각 기록이 갱신이 될 때마다 updated_at 값이 갱신이되겠다.
 장점으로는 symfony 가 이러한 컬럼들의 이름을 인지하고 그들의 갱신을 다루어준다. 수동으로created_at 과 updated_at 컬럼들을 설정할 필요 없이 자동으로 갱신이 될 것이다. 동일하게 created_on,updated_on 으로 명명된 컬럼들에게도 적용이 된다.

 * created_at, updated_at 컬럼들은 자동으로 다루어진다. *
$comment = new Comment();
$comment->setAuthor('Steve');
$comment->save();
 
// Show the creation date
echo $comment->getCreatedAt();
  => [date of the database INSERT operation]

추가적으로, 날짜 컬럼덜에 대한 getter 들은 매개변수로 날자형을 받아들인다.
echo $comment->getCreatedAt('Y-m-d');


<데이터 계층으로 재요소화 하기>
symfony프로젝트를 개발할 때, 때때로 action 안에 영역 로직 코드를 작성함으로 시작하게 된다. 그러나 데이터베이스 질의와 모델생성은 콘트롤러 계층에 저장이 되어서는 안된다. 따라서 모든 데이터와 관련된 로직들은 모델 계층으로 옮겨와야 한다. action에서 하나 이상의 곳에서 같은 요청을 할 필요가 있을 때마다, 모델로의 관련 코드를 전달하는 것에 대해서 생각해보라. 이것이action 을 간결하고 읽기 쉬운 코드로 유지할 수 있게 해준다.
예를 들어 블로그에서 주어진 태그에서 가장 인기있는 열개의 블로그를 가져오는 코드를 생각해보자. 코드는 action 이 아닌 모델에 존재해야 한다. 만약 템플릿에 출력할 필요가 있따면 action 은 단순히 다음과 같아야 한다 :
public function executeShowPopularArticlesForTag()
{
  $tag = TagPeer::retrieveByName($this->getRequestParameter('tag'));
  $this->foward404Unless($tag);
  $this->articles = $tag->getPopularArticles(10);
}
action은 request 매개변수로 부터 tag 클래스의 객체를 생성하고, 데이터베이스 질의에 필요한 모든 코드들은getPopularArticles() 에 위치하게 된다. 이것은 action 을 더욱 읽기 쉬운 코드로 만들어 주고, 모델코드가 다른 action 에서도 쉽게 재사용 될 수 있게 해준다. 코드들을 더 적당한 위치로 옮기는 것이 재요소화의 기법중하나이다. 만약 더 자주 이것을 한다면 코드가 더욱 유지보수 하기 쉬울 것이고 다른 개발자들이 이해하기도 쉬울 것이다. 데이터계층 재요소화의 첫째 가는 원칙은 action 의 코드는 php 코드로서 10줄 이상을 넘어서는 일이 거의 없어야 한다는것이다.

Posted by nkdk
TAG symfony
2009/07/24 16:41

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

My Programing/PHP2009/07/24 15:59

이번에는 간단한 arraylist와 hashmap을 먼저하고 db연결하는 방법에 대해서 보려 한다.

<?php

    $array = array(1, 10, 100, 1000, 100000);
    print_r($array);
    echo "<br>";
    $array2 = array(1, 2, 3, 4, 5, 8=> 1, 4=>1, 19, 3=>13);
    print_r($array2);
    echo "<br>";
    $array3 = array("data1" => 555, "data2" => 777, "data3" => 999);
    print_r($array3);
    $c->priority = $hashmap[$c->name];
    echo "<br>".$array3["data2"]."<br>";
   
    //"zzz"값을 갖는 원소의 key 이름은
    echo "key name=".array_search(999,$array3);

    echo "<br>".array_values($array3);
   
    $keyval = "data3";
    if (array_key_exists($keyval, $array3)) {
        echo "<br>the element is exists. value=".$array3[$keyval];
    }
   
    // array_search() 함수가 php.net 함수리스트 목록상에는 나타나있지 않더군요.
    // 참고로 그냥 array에 원소가 있는지 없는지 true/false 값만의 검사는
    // in_array() 사용.
?>

array변수 같은 경우는 보통의 배열 같은 변수이고..
array[0] 이런식으로 꺼내오면 된다.
array2변수 같은 경우는 번호로 숫자를 지정해 줬다. 보통 arrayList와 비슷한 방식이다.
array3변수 같은 경우는 hashmap처럼 키값으로 끄집어 낼수 있게 되어있다.

키로 꺼내는 법과 키가 있는가 존재 여부확인 후 꺼내는 법도 위에 있으니 참조바람..

다음은 db연결하는 법입니다.

phpDbConnect.php
<html>
<head>
<title>DB Simple connect</title>
</head>
<body>

<?

    if(!mysql_connect("localhost","root",""))
    {
        echo "<h2>"."db Connect error"."</h2>";
        die();
    }
    mysql_select_db("cdcol");
?>

<h2>DB SIMPLE Connect</h2>

<table>
<tr bgcolor=#f87820>
<td>&nbsp;</td>
<td class=tabhead>title</td>
<td class=tabhead>name</td>
<td class=tabhead>year</td>
<td class=tabhead>status</td>
<td></td>
</tr>

<?
    if($_REQUEST['interpret']!="")
    {
        if($jahr=="")$jahr="NULL";
        $titel=htmlentities($_REQUEST['titel']);
        $interpret=htmlentities($_REQUEST['interpret']);
        $jahr=htmlentities($_REQUEST['jahr']);
        mysql_query("INSERT INTO cds (titel,interpret,jahr) VALUES('$titel','$interpret',$jahr);");
    }

    if($_REQUEST['action']=="del")
    {
        mysql_query("DELETE FROM cds WHERE id={$_REQUEST['id']};");
    }

    $result=mysql_query("SELECT id,titel,interpret,jahr FROM cds ORDER BY interpret;");
   
    $i=0;
    while( $row=mysql_fetch_array($result) )
    {
        if($i>0)
        {
            echo "<tr valign=bottom>";
            echo "<td colspan=6></td>";
            echo "</tr>";
        }
        echo "<tr valign=center>";
        echo "<td class=tabval>&nbsp;</td>";
        echo "<td class=tabval><b>".$row['interpret']."</b></td>";
        echo "<td class=tabval>".$row['titel']."&nbsp;</td>";
        echo "<td class=tabval>".$row['jahr']."&nbsp;</td>";

        echo "<td>OK</td>";
        echo "<td class=tabval></td>";
        echo "</tr>";
        $i++;

    }
    echo "<tr valign=bottom>";
        echo "<td bgcolor=#fb7922 colspan=6><img src=img/blank.gif width=1 height=8></td>";
        echo "</tr>";
?>
</table>
</body>
</html>

이 정도가 되겠습니다.
Posted by nkdk
TAG php
My Programing/PHP2009/07/24 14:44
이번에는 php에서 include하는 방법과 log를 남기는 것에 대해서 볼까 한다.

일단 log남기는 것 부터...

get_log.php
<?php

    $log = date("Y/m/d H:i:s")."\t";
    $log .= $_SERVER["HTTP_USER_AGENT"]."\t";
    $log .= $_SERVER["HTTP_REFERER"]."\t";
    $log .= gethostbyaddr($_SERVER["REMOTE_ADDR"])."\n";
    $fh = fopen("log.txt", "a");
    flock($fh, LOCK_EX);
    fputs($fh, $log);
    flock($fh, LOCK_UN);
    fclose($fh);

?>
이걸로 일단 기본적인 파일을 만드세요. 이걸 계속 include해서 사용할 거임.

logPhp.php
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Php Etc Form Test</title>
</head>
    <body>
<?php

    include "get_log.php";

?>log save complete
<br>
<a href="display_log.php">displayLog</a>
    </body>
</html>

display_log.php
<?php

$str = <<<EOD
<table border=4 width=800 align=center
<caption>dialog log</caption>
<tr bgcolor="#888888">
    <th>time</th>
    <th>user Explorer</th>
    <th>__</th>
    <th>IP</th>
</tr>
EOD;
echo $str;

$logs = @file("log.txt") or die("log file error");
foreach($logs as $line) {
    list($time, $agent, $referer, $ip) = explode("\t", $line);
    if(!$referer) $referer="<br>";
    echo "<tr align=center><td>$time</td><td>$agent</td><td>$referer</td><td>$ip</td></tr>";
}

?>

이렇게 하고 logPhp.php를 실행해 보면 된다.

자 다음으로는 간단한 db연결 및 hashmap과 같이 쓰는법 array의 원리..  정도? 할까 생각 중
Posted by nkdk
TAG php