@blog.justoneplanet.info

日々勉強

PHPでStrategyパターン

アルゴリズムをクラスとして定義し、対象によって切り替える。

■実装

class ItemDataContext
{
    private $_strategy;

    public function __construct(ReadItemDataStrategy $strategy){
        $this->_strategy = $strategy;
    }

    public function getItemData(){
        return $this->strategy->getData();
    }
}
abstract class ReadItemDataStrategy
{
    private $_filename;
    public function __construct($filename)
    {
        $this->_filename = $filename;
    }

    public function getData()
    {
        if(!is_readable($filename = $this->getFilename())){
            throw new Exception('is not readable : ' . $filename);
        }
        return $this->readData($filename);
    }

    public function getFilename()
    {
        return $this->_filename;
    }

    protected abstract function readData($filename);
}

ReadFixedLengthDataStrategy.php

class ReadFixedLengthDataStrategy extends ReadItemDataStrategy
{
    protected function readData($filename){
        // implementation
    }
}

ReadTabSeparatedDataStrategy.php

class ReadTabSeparatedDataStrategy extends ReadItemDataStrategy
{
    protected function readData($filename){
        // implementation
    }
}

MySQLサーバーをインストールして初期設定をする

■インストール

yum install mysql-server

PHPから使う場合は以下のようにphp-mysqlもインストールする。

yum install php-mysql

MySQLにアクセスする。

mysql -u root

■パスワード

アクセスできるのは良いんだがパスワードが設定されてないのはイカン。(・ε・)

SET PASSWORD FOR root@localhost=PASSWORD('fugafuga');

■データベース

ちょっとデータベースを作ってみる。

CREATE DATABASE `sample`;

■テーブル

ちょっとテーブルを作ってみる。

USE `sample`;
CREATE TABLE  `sample`.`tbl1` (
    `id` INT( 11 ) NOT NULL AUTO_INCREMENT ,
    `name` VARCHAR( 255 ) NOT NULL ,
    PRIMARY KEY (  `id` )
) ENGINE = MYISAM ;

■文字コード

ネットで探してもなかなか適切な文献が見当たらない。凄く深いので結論だけまとめておく。

ちょっとPHPからデータを入れてみる。

$dbh = mysql_connect(DB_HOST, DB_USER, DB_PASS);
mysql_select_db(DB_NAME, $dbh);
$query = "INSERT INTO `tbl1`(`name`) VALUES('山田');";
$result = mysql_query($query);

phpMyAdminで見てみると以下のように文字化けする。

capture

MySQLのデフォルト文字コードはlatin1になっている為だ。アプリケーション側で表示するときは特に文字化けすることはないかもしれないが、管理上は非常に面倒なのでデフォルト文字コードをutf-8にする事を強く勧める。

my.cnf(my.ini)

以下のようにする。

[mysqld]
character-set-server=utf8
[client]
default-character-set=utf8

但しMySQLクライアントのコンパイルオプションによっては全然文字化けしまくる。従ってプログラム側での修正も必要だ。

PHP

mysql_connectを使う

mysql_connectを使用している場合は5.2.3以上が必須で、以下のようにクライアント側の文字コードを設定する。

$dbh = mysql_connect(DB_HOST, DB_USER, DB_PASS);
mysql_set_charset('utf8', $dbh);
PDOを使う

PDOを使用している場合は以下のように、MySQLの設定ファイルを読み込み、ATTR_EMULATE_PREPARESをfalseにしてサーバーサイドPrepared Statementを使うようにする。

$dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME;
$dbh = new PDO(
    $dsn,
    DB_USER,
    DB_PASS,
    array(
        PDO::MYSQL_ATTR_READ_DEFAULT_FILE => '/etc/my.cnf'
    )
);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

但し、MySQL<5.1の場合はクエリキャッシュが聞かないので注意が必要だ。現時点ではPDOでクライアント側の文字コードを指定する方法は無い。

誤り
  • 「SET NAMES utf8」クエリを発行する。
  • 「skip-character-set-client-handshake」をmy.cnfに記述する。
参考

「PHP5.2.3以前」かつ「PDOを使用できない」かつ「再コンパイルできない」とき文字コードの問題を完全に解決するのは不可能とのこと。

CentOSにおけるパッケージインストールまとめ

■ソースからインストール

ソースパッケージをダウンロードしてコンパイル・ビルド。(gcc・・・)

【メリット】

  • 環境に依存したどうこうに関係なくインストールできる。

インストールするときにインストールするマシン自身が自分でビルドする為。

【デメリット】

  • 「依存関係」の考慮が必要で面倒
  • 必要な他プログラムなどあれば別途自分でインストールする等

■RPMでインストール

あらかじめ別のマシンでコンパイルしたものをそのままコピーする。

【メリット】

  • 自分でコンパイルしないだけ簡単

【デメリット】

  • コンパイルしたマシンと環境が異なる場合は使えない
  • コンパイルしたマシンで指定したオプションに限定され、必要なオプションが指定できない
  • パッケージはあらゆる人が公開してるから、探したりダウンロードしたり記録したり、などが面倒

■yumでインストール

パッケージ群を公開しているリポジトリからRPMを探し、ダウンロードして、インストールしてくれるツール

【デメリット】

  • 依存関係や付属プログラムや、そもそもそれらの有無なども、すべて他人が決めたもの
  • たくさんのリポジトリを参照するようにyumに追加設定すると依存関係のトラブルが起きたりする

【ポイント】

リポジトリは信頼できるところにしておいたほうが良い。

  • ~redhat.com
  • 有名どころとか
  • 各ディストリビューションのベンダーなど・・・

Windows XPでPHPとimageMagickを使用する

■ImageMagick

以下のサイトからWindows版をダウンロードしてインストールする。

http://www.imagemagick.org/script/binary-releases.php?ImageMagick=pjjjn3udinf3ldej2osq7k8nj1#windows

Program Files配下などのスペースを含むパスにインストールせず、cドライブ直下が望ましいらしい。

環境変数

設定>コントロールパネル>システム>詳細設定>環境変数>システム環境変数

変数名はMAGICK_HOMEとし値はインストールしたパスを入力する。

■Microsoft Visual C++ 2005 SP1

以下のサイトからインストール

http://www.microsoft.com/downloads/details.aspx?familyid=200B2FD9-AE1A-4A14-984D-389C36F85647&displaylang=ja

■php_imagick

以下のサイトからインストールする。

http://www.sk89q.com/2010/03/vc6-windows-binaries-for-imagick-2-3-0/

リンクが切れている場合はここ

上手くいかない場合は以下のサイトのdllを使用した方が良いかもしれない。

http://valokuva.org/?page_id=50

extension_dir = "C:\xampp\php\ext"

php.iniが上述のような場合は「C:\xampp\php\ext」にファイルを配置する。

php.ini

以下の設定を追加(パスは環境に合わせて変更してください)

extension=php_imagick.dll

Windowsを再起動すると使用可能になる。

PHPでProxyパターン

■実装

とりあえずパターンの要ではないクラス。

class Item
{
    private $_id;
    private $_name;
    public function __construct($id, $name)
    {
        $this->_id   = $id;
        $this->_name = $name;
    }

    public function getId()
    {
        return $this->_id;
    }

    public function getName()
    {
        return $this->_name;
    }
}

ItemDao.php

interface ItemDao
{
    public function findById($id);
}
DbItemDao.php

DBからアイテムデータを取得して生成したインスタンスを返す本番用クラス。

class DbItemDao implements ItemDao
{
    public function findById($id)
    {
        $fh = fopen('data.txt', 'r');
        $item = null;
        while($buffer = fgets($fh, 4096)){
            $id1  = trim(substr($buffer, 0, 10));
            $name = trim(substr($buffer, 10));
            if($id === (int) $id1){
                $item = new Item($id1, $name);
                break;
            }
        }
        fclose($fh);
        return $item;
    }
}
MockItemDao.php

ダミーデータで作ったインスタンスを返すテスト用クラス。

class MockItemDao implements ItemDao
{
    public function findById($id)
    {
        $item = new Item($id, 'dummy');
        return $item;
    }
}

ItemDaoProxy.php

class ItemDaoProxy
{
    private $_dao;
    private $_cache;
    public function __construct(ItemDao $dao)
    {
        $this->_dao   = $dao;
        $this->_cache = array();
    }

    public function findById($id)
    {
        if(array_key_exists($id, $this->_cache)){
            return $this->_cache[$id];
        }
        $this->_cache[$id] = $this->dao->findById($id);
        return $this->_cache[$id];
    }
}

■クライアントコード

以下のようにすることで使うことができる。

switch($mode){
    case 'test':
        $dao = new MockItemDao();
        break;
    case 'production':
        $dao = new DbItemDao();
        break;
}
$proxy = new ItemDaoProxy($dao);

PHPでObserverパターン

class Cart
{
    private $_items
    private $_listeners;

    public function __construct()
    {
        $this->_items     = array();
        $this->_listeners = array();
    }

    public function add($cd)
    {
        $this->_items[$cd] = (isset($this->_items[$cd]))? ++$this->_items[$cd] : 1;
        $this->notify();
    }

    public function remove($cd)
    {
        $this->_items[$cd] = (isset($this->_items[$cd]))? --$this->_items[$cd] : 0;
        if($this->_items[$cd] <= 0){
            unset($this->_items[$cd]);
        }
        $this->notify();
    }

    public function get()
    {
        return $this->_items;
    }

    public function has($cd)
    {
        return array_key_exists($cd, $this->_items);
    }

    public function notify()
    {
        foreach($this->_listeners as $listener){
            $listener->update($this);
        }
    }

    public function addListener(CartListener $listener)
    {
        $this->_listeners[get_class($listener)] = $listener;
    }
}

Listener.php

以下のようにインターフェースを定義する。

interface Listener
{
    public function update(Cart $cart);
}

以下のようにListenerを実装する。

PresentListener
class PresentListener implements Listener
{
    const PRESENT_TARGET = '30:cookie';
    const PRESENT_ITEM   = '99:present';

    public function __construct()
    {
    }

    public function update(Cart $cart)
    {
        if($cart->hasItem(self::PRESENT_TARGET) && !$cart->hasItem(self::PRESENT_ITEM)){
            $cart->add(self::PRESENT_ITEM);
        }
        if(!$cart->hasItem(self::PRESENT_TARGET) && $cart->hasItem(self::PRESENT_ITEM)){
            $cart->remove(self::PRESENT_ITEM);
        }
    }
}
LoginListener
class LoginListener implements Listener
{
    public function __construct()
    {
    }

    public function update(Cart $cart)
    {
        var_dump($cart->get());
    }
}

androidでアプリを作ってみる

今回のアプリはandroid marketで公開した「prime?」という名のアプリの全てのソースコードである。

■AndroidManifest.xml

今回はActivityを2つ使用するので、以下のようにactivity要素を加えないとアプリから使用できない。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="info.justoneplanet.android.primenumber"
      android:versionCode="2"
      android:versionName="0.2">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".PrimeNumberActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".PrimeNumberListActivity"
                  android:label="@string/app_name_list">
        </activity>
    </application>
    <uses-sdk android:minSdkVersion="3" />
</manifest>

■アクティビティ1

/src/info/justoneplanet/android/primenumber/PrimeNumberActivity.java

まず、初めに表示されるActivityを実装する。

package info.justoneplanet.android.primenumber;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class PrimeNumberActivity extends Activity {
    private static final int MENU1_ID = Menu.FIRST;
    private static final int MENU2_ID = Menu.FIRST + 1;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);// こいつでレイアウトのxmlを指定!

        Button validate = (Button) findViewById(R.id.button_validate);
        validate.setOnClickListener(// これがイベントだ!なんかコード量多し。
            new View.OnClickListener() {
                public void onClick(View v) {
                    TextView textView = (TextView) findViewById(R.id.result);
                    EditText editText = (EditText) findViewById(R.id.number);
                    Integer number = Integer.valueOf(editText.getText().toString());
                    int num = _isPrime(number, textView);
                    if(num == 0){
                        textView.setText("Prime!!");
                    }
                    else if(num < 0){
                        textView.setText("Not Prime!!");
                    }
                    else{
                        String str = Integer.toString(num);
                        textView.setText("Not Prime!! It will be divided by '" + str + "'");
                    }
                }
            }
        );
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        // メニューを押したとき
        boolean result = super.onCreateOptionsMenu(menu);
        menu.add(0, MENU1_ID, Menu.NONE, R.string.menu2);
        return result;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        // メニューが選択されたとき
        switch(item.getItemId()){
        case MENU1_ID:
            //Log.e("tag", "start");
            startActivity(new Intent(this, PrimeNumberListActivity.class));
            finish();
            return true;
        }
        return false;
    }

    /**
     *_isPrime
     * @param number
     * @return
     */
    private int _isPrime(Integer number, TextView textView)
    {
        if(number < 2){
            return -1;
        }
        for(int i = 2; i < number / 2 + 1; i++){
            textView.setText(Integer.toString(i));
            if(number % i == 0){
                return i;
            }
        }
        return 0;
    }
}

/res/layout/main.xml

上述のActivityのレイアウトを定義する。ボタンが上にあると押しにくいので下に配置した。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:id="@+id/result"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/msg_title_input"
    android:textSize="16dip"
    />
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="bottom"
        >
    <EditText
        android:id="@+id/number"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="10dip"
        android:inputType="number"
        android:textSize="26dip"
        android:maxLength="7"
        />
    <Button
        android:id="@+id/button_validate"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/button_validate"
        />
    </LinearLayout>
</LinearLayout>

■/src/info/justoneplanet/android/primenumber/PrimeNumberListActivity.java

まず、次のActivityを実装する。せっかくだからListActivityを継承する。

package info.justoneplanet.android.primenumber;

import java.util.ArrayList;

import android.app.Activity;
import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.text.Layout;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

public class PrimeNumberListActivity extends ListActivity {
    private static final int MENU1_ID = Menu.FIRST;
    private static final int MENU2_ID = Menu.FIRST + 1;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.list);
        
        // 配列を生成
        ArrayList<String> numbers = new ArrayList<String>();
        for(int i = 0; i < 100; i++){
            numbers.add(Integer.toString(i));
        }
        // 生成した配列をリストの各要素にassign
        setListAdapter(new ArrayAdapter<String>(
            getApplicationContext(),
            R.layout.list_row,
            numbers
        ));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        boolean result = super.onCreateOptionsMenu(menu);
        menu.add(0, MENU1_ID, Menu.NONE, R.string.menu1);
        return result;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        switch(item.getItemId()){
        case MENU1_ID:
            startActivity(new Intent(this, PrimeNumberActivity.class));
            finish();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id)
    {
        // リストがクリックされたとき
        super.onListItemClick(l, v, position, id);
        TextView textView = (TextView) findViewById(R.id.result);
        Integer strItem = Integer.valueOf((String) getListAdapter().getItem(position));
        int num = _isPrime(strItem, textView);
        if(num == 0){
            textView.setText("Prime!!");
        }
        else if(num < 0){
            textView.setText("Not Prime!!");
        }
        else{
            String str = Integer.toString(num);
            textView.setText("Not Prime!! It will be divided by '" + str + "'");
        }
    }

    /**
     *_isPrime
     * @param number
     * @return
     */
    private int _isPrime(Integer number, TextView textView)
    {
        if(number < 2){
            return -1;
        }
        for(int i = 2; i < number / 2 + 1; i++){
            textView.setText(Integer.toString(i));
            if(number % i == 0){
                return i;
            }
        }
        return 0;
    }
}

まぁ、ツッコミどころは沢山あるがとりあえず。

/res/layout/list.xml

上述のActivityのレイアウトを定義する。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:id="@+id/result"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/msg_title_select"
    android:textSize="16dip"
    />
<ListView
    android:id="@id/android:list"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
<TextView
    android:id="@+id/android:empty"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/msg_title_select"
    android:textSize="20dip"
    />
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:id="@+id/buttons"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        >
    </LinearLayout>
</LinearLayout>

/res/layout/list_row.xml

ListActivityを使用した場合はリストの行のレイアウトを定義する必要がある。文字が小さいとリストが選択しにくいので大きめサイズだ~

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="24dip"
    android:text="@string/msg_title_select"
    />

■/res/values/strings.xml

多言語対応できるように文字は以下のファイルに記述するべきだ。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">prime number</string>
    <string name="app_name_list">prime number list</string>
    <string name="msg_title_input">Input a number!</string>
    <string name="msg_title_select">Select a number!</string>
    <string name="button_validate">Validate</string>
    <string name="result_prime">Prime!</string>
    <string name="result_notprime">Not Prime!</string>
    <string name="menu1">Input</string>
    <string name="menu2">List</string>
</resources>

今回はコードにも書いちゃってるけど気にしない♪

■/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:id="@+id/result"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/msg_title_input"
    android:textSize="16dip"
    />
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="bottom"
        >
    <EditText
        android:id="@+id/number"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="10dip"
        android:inputType="number"
        android:textSize="26dip"
        android:maxLength="7"
        />
    <Button
        android:id="@+id/button_validate"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/button_validate"
        />
    </LinearLayout>
</LinearLayout>

Activityは画面だ★

PHPとRedisで登録フォームとログインフォームを作ってみる

5分位で書いてみた。セキュリティもログイン状態の維持も気にしてない。ただ概念上のサンプルとして書いておく。(ヽ´ω`)

■登録フォーム

<?php
if($_SERVER['REQUEST_METHOD'] ==='POST'){
    $redis = new Redis();
    $redis->connect('localhost', 6379);
    if($redis->setnx(trim($_POST['name']), md5(trim($_POST['pass'])))){
        $msg = 'registered!';
    }
    else{
        $msg = 'not registered!';
    }
}
else{
        $msg = 'Input!';
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>登録フォーム</title>
</head>

<body>
<form action="./register.php" method="post">
<p><?php print($msg); ?></p>
<dl>
<dt>name</dt>
<dd><input type="text" name="name" /></dd>
<dt>pass</dt>
<dd><input type="password" name="pass" /></dd>
</dl>
<p><input type="submit" name="submit" value="submit" /></p>
</form>
</body>
</html>

■ログインフォーム

<?php
if($_SERVER['REQUEST_METHOD'] ==='POST'){
    $redis = new Redis();
    $redis->connect('localhost', 6379);
    if($value = $redis->get(trim($_POST['name']))){
        if($value === md5(trim($_POST['pass']))){
            $msg = 'Login!';
        }
        else{
            $msg = 'Wrong!(pass)';
        }
    }
    else{
        $msg = 'Wrong!(name)';
    }
}
else{
        $msg = 'Input!';
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>ログインフォーム</title>
</head>

<body>
<form action="./login.php" method="post">
<p><?php print($msg); ?></p>
<dl>
<dt>name</dt>
<dd><input type="text" name="name" /></dd>
<dt>pass</dt>
<dd><input type="password" name="pass" /></dd>
</dl>
<p><input type="submit" name="login" value="login" /></p>
</form>
</body>
</html>

すごく色々考えるとGAEが最強な気がする。

ファイル数を調べる

以下のコマンドで任意のディレクトリのファイル数を調べることができる。

find ./httpdocs -type f | wc -l