ラベル PHP の投稿を表示しています。 すべての投稿を表示
ラベル PHP の投稿を表示しています。 すべての投稿を表示

2023年10月9日月曜日

在庫管理システムSASO2.4.0リリース

 本日、在庫管理システムSASO2.4がリリースされました。
以下のリンクからお越しください。

在庫管理システムSASO2.4

フリーロケーションのシンプルな在庫管理システムSASO

bootstrap5を使ったクールなデザイン。
クリーンアーキテクチャとモナドによる美しく※1、そして透明感※2のあるコード。

それが今、あなたの手に。

※1 当店比。
※2 参照透過性のこと。

2013年10月30日水曜日

在庫管理システム『SASO1.0』コントローラクラス

コントローラクラスは全て、スーパークラスのControllerBaseクラスを継承します。
ControllerBaseクラスの役割は主に、レガシーPHPであるlogicディレクトリの中身、及びビューの呼び出しです。
そして、エラーページを呼び出す役割もあります。
ビューはviewディレクトリ内のtemp.phpというテンプレートファイルから呼び出されます。
各ページのタイトルなどはtemp.phpに記述されます。

<?php
namespace saso\classes\controllers;
use \saso\classes\base;

class ControllerBase{
    private $_title;
    private $_content;
    private $_errorMessage;
    
    private function _linkTo($path){
        return 'http://' . $_SERVER['HTTP_HOST'] . '/' . PROGRAM_DIR . '/' . $path; 
    }
    private function _getContentTitle() {
        return $this->_title;
    }
    private function _getContent() {
        return $this->_content;
    }
    protected function _setContentTitle($title) {
        $this->_title = $title;
    }
    protected function _setCurrentContent($current) {
        require_once 'logic/' . $this->_controllerName . '/' . $current.'.php';
        $this->_content = 'view/' . $this->_controllerName . '/' . $current.'.php';
        require_once 'view/temp.php';
    }
    protected function _invalidAccess(){
        $postTest = new base\Post();
        $nameTest = new base\Request($postTest);
        if(is_null($nameTest->getRequest())){
            header('Location: http://'.$_SERVER['HTTP_HOST'] . '/' . PROGRAM_DIR . '/' . $this->_controllerName . '/start/',TRUE,'303');
        }
    }
    public function error($errorCode){
        switch($errorCode){
            case 'database':
                $this->_errorMessage = 'データベースに接続できませんでした。';
                break;
            case 'invalid':
                $this->_errorMessage = '入力値が不正です。';
                break;
            case 'notFound':
                $this->_errorMessage = 'ページが見つかりません。';
                break;
            case 'invalidClone':
                $this->_errorMessage = 'クローンは使えません。';
                break;
            default:
                $this->_errorMessage = '予期せぬエラーが発生しました。';
                break;
        }
        $this->_title = 'エラー';
        $this->_content = 'view/error.php';
        require_once 'view/temp.php';
    }
}

少々長いControllerBaseクラスに対し、それぞれのコントローラクラスは非常に単純です。
やっていることは、タイトルの設定と、呼び出すlogicとviewディレクトリ以下のファイル名のセットです。
「$this->_invalidAccess();」を設定することでPOSTなしでアクセスするのを防ぎます。
(startAction()に飛ばされます)

<?php
namespace saso\classes\controllers;

class ItemController extends ControllerBase{
    protected $_controllerName = 'item';

    public function startAction(){
        $this->_setContentTitle('商品管理');
        $this->_setCurrentContent('start');
    }
    //商品登録
    public function addItemAction(){
        $this->_setContentTitle('商品登録');
        $this->_setCurrentContent('addItem');
    }
    public function addItemConfirmAction(){
        $this->_invalidAccess();
        $this->_setContentTitle('登録確認');
        $this->_setCurrentContent('addItemConfirm');
    }
    public function addItemSaveAction(){
        $this->_invalidAccess();
        $this->_setContentTitle('登録完了');
        $this->_setCurrentContent('addItemSave');
    }
}


2013年10月25日金曜日

在庫管理システム『SASO1.0』POSTとGETの受け取り

POSTとGETの受け取りもクラスにします。
手順としては、まずPOSTはPostクラス、GETはUrlParameterクラスが受け取ります。
(リクエスト振り分けクラスDispatcherがGETはスラッシュ区切りの第3パラメータ以降としていたのでした。)

次に、RequestクラスがPostインスタンス、UrlParameterインスタンスを引数に取り、
そののちgetRequest()メソッドで使えるようにします。

ここで、Requestクラスは2種類のインスタンスを引数に取りますが、
その2種類にはsetValues()メソッドを持って欲しいのです。
そこで、RequestInterfaceを定義します。

<?php
namespace saso\classes\base;

interface RequestInterface{
    function setValues();
}
それを実装したものが以下の二つです。

<?php
namespace saso\classes\base;

class Post implements RequestInterface{
    private $_values;
    
    public function setValues(){
        foreach($_POST as $key => $value){
            $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
            $this->_values[$key] = $value;
        }
        return $this->_values;
    }
}

<?php
namespace saso\classes\base;

class UrlParameter implements RequestInterface{
    private $_values;
    
    public function setValues(){
        foreach($_GET as $key => $value){
            $this->_values[$key] = $value;
        }
        
        return $this->_values;
    }
}
Requestクラスはコンストラクタでインターフェースをタイプヒンティングしています。
<?php
namespace saso\classes\base;

class Request{
    private $_request;
    
    public function __construct(RequestInterface $r){
        $this->_request = $r->setValues();
    }
    private function _hasKey($key){
        if(false == array_key_exists($key,$this->_request)){
            return false;
        }
        return true;
    }
    public function getRequest($key = null){
        if(null == $key){
            return $this->_request;
        }//キーを指定されなかったら、全部を配列で返す。
        if(false == $this->_hasKey($key)){
            return null;
        }//言われたキーがない場合nullを返す
        return $this->_request[$key];//普通は言われたキーのヴァリューを返す
    }
}
使い方は以下の通りです。
<?php
$post = new base\Post();
$name = new base\Request($post);
echo $name->getRequest('itemName');

参考:http://www.objective-php.net/

2013年10月23日水曜日

在庫管理システム『SASO1.0』リクエスト振り分けクラス

今回は在庫管理システムのリクエスト自動振り分けクラスDispatcherです。
少々長いです。

まず、setSystemRoot()はindex.phpで使用していました。
このシステムのルートディレクトリをセットするメソッドです。

dispatch()は受け取ったリクエストのルートより後を、
スラッシュ区切りでコントローラやアクション(コントローラ内のメソッド)に振り分けるのが
主な仕事です。

余分なところを取り除いた後で、スラッシュ区切りで配列に収納します。
具体的には、
http://****/saso1_0/
を取り除きます。
その後のリクエスト、例えば、
item/addItem/
であれば、まずitemの頭文字を大文字にして、"Controller"をくっつけてクラスを呼び出します。
そして、addItemは"Action"をくっつけてそのコントローラのメソッドを使います。
デフォルトは双方とも"start"としています。
つまり、item/だけであれば、ItemControllerクラスのstartAction()が呼ばれます。

さて、二つのパラメータの後にもスラッシュでリクエストが続いていたら、
以降はスーパーグローバル変数の$_GETに代入します。
スーパーグローバル変数には代入できないのかと思いましたが、$_GETには代入できるようです。
スラッシュで区切りながら、収納していきます。

<?php
namespace saso\classes\base;

class Dispatcher{
    private $_sysRoot;
    
    public function setSystemRoot($path){
        $this->_sysRoot = rtrim($path, '/');
    }
    public function dispatch(){
        $param = ereg_replace('/?$', '', $_SERVER['REQUEST_URI']);
        $param = ereg_replace(PROGRAM_DIR, '', $param);
        $param = ereg_replace('^/*', '', $param);
        
        $params = array();
        if('' != $param){
            $params = explode('/', $param);
        }
        
        
        //_GET(UrlParameter用)
        $_GET[0] = '';
        if(2 < count($params)){
            for($i = 0; $i < count($params) - 2; $i++){
                $_GET[$i] = $params[$i+2];
            }
        }
       
       
        //controller
        if(isset($params[0]) && 'start' == $params[0] && 'start' == $params[1]){
            header('Location: http://'.$_SERVER['HTTP_HOST'] . '/' . PROGRAM_DIR . '/',TRUE,'301');
        }
        
        $controller = 'start';
        if(0 < count($params)){
            $controller = $params[0];
        }
        
        $className = ucfirst(strtolower($controller)) . 'Controller';
        $className = '\\saso\\classes\\controllers\\' . $className;
        try{
            $controllerInstance = new $className();
        }catch(\Exception $e){
            $ctrlbase = new \saso\classes\controllers\ControllerBase();
            $ctrlbase->error($e->getMessage());
            die();
        }
        
        
        //action
        if(isset($params[0]) && !isset($params[1])){
            header('Location: http://'.$_SERVER['HTTP_HOST'] . '/' . PROGRAM_DIR . '/' . $controller . '/start/',TRUE,'301');
        }
        $action = 'start';
        if(1 < count($params)){
            $action = $params[1];
        }
        
        $actionMethod = $action . 'Action';
        try{
            if(!method_exists($controllerInstance, $actionMethod)){
                throw new \Exception('notFound');
            }
            $controllerInstance->$actionMethod();
        }catch(\Exception $e){
            $ctrlbase = new \saso\classes\controllers\ControllerBase();
            $ctrlbase->error($e->getMessage());
            die();
        }
    }
}

以下のサイトを大いに参考にしました。PHPのオブジェクト指向、MVCアーキテクチャーについてわかりやすく説明しています。
http://www.objective-php.net/

2013年10月22日火曜日

在庫管理システム『SASO1.0』入口のファイル

SASO1.0では階層のトップに3つのPHPファイルを置いています。
index.phpは以下の通りです。
定数設定ファイルのconfig.phpとオートローダのautoload.phpを呼び出しています。
後はリクエストに応じて、Dispatcherクラスが振り分けてくれます。
<?php    
use \saso\classes\base;
require_once 'config.php';
require_once 'autoload.php';
$dispatcher = new base\Dispatcher();
$dispatcher->setSystemRoot(DOCUMENT_ROOT . PROGRAM_DIR);
$dispatcher->dispatch();

次にオートローダである、autoload.phpです。
クラスを使うときは名前空間とディレクトリ構造が一致している必要があります。
もちろん、一つのクラスにつき一つのファイルです。
<?php
function __autoload($classname) {
    $classname = ltrim($classname, '\\');
    $filename  = '';
    $namespace = '';
    if ($lastnspos = strripos($classname, '\\')) {
        $namespace = substr($classname, 0, $lastnspos);
        $classname = substr($classname, $lastnspos + 1);
        $firstnspos = stripos($namespace, '\\');
        $namespace = substr($namespace, $firstnspos + 1);
        $filename  = DOCUMENT_ROOT . PROGRAM_DIR . '/' . str_replace('\\', '/', $namespace) . '/';
    }
    $filename .= $classname . '.php';
    if(!file_exists($filename)){
        throw new \Exception('notFound');
    }
    require $filename;
}

ちなみに、config.phpは以下の通りです。

<?php
const
//パス
     DOCUMENT_ROOT = '/***/***/public_html/'
    ,PROGRAM_DIR = 'saso1_0'
//データベース
    ,DSN     = 'mysql:dbname=saso;host=localhost'
    ,USER    = 'saso_user'
    ,PASSWORD= '**********'
    ;

2013年10月21日月曜日

在庫管理システム『SASO1.0』PDOを使う。

SASO1.0ではデータベース抽象レイヤーにPDOを使っています。
シングルトンパターンを使って、作ってみました。
クローンを禁止しています。

なお、オートローダがディレクトリ構造を把握したいので、名前空間を使っています。
デフォルトフェッチモードをクラスとして作ります。

catch内はコントローラクラスのスーパークラスにエラー処理をまとめているので、こうしました。

このファイルはclassesのbaseディレクトリに入れます。


<?php
namespace saso\classes\base;
class PdoManager{    
    private static $_pdo;
    
    private function __construct() {
        try{
            self::$_pdo = new \PDO(
                DSN,
                USER,
                PASSWORD,
                array(
                    \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                    \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_CLASS,
                )
            );
            self::$_pdo->exec('SET NAMES utf8');
        }catch (\PDOException $e){
            $ctrlbase = new \saso\classes\controllers\ControllerBase();
            $ctrlbase->error('database');
            die();
        }
    }
    
    public static function getPdo() {
        new PdoManager();
        return self::$_pdo;
    }
        
    public final function __clone() {
        throw new \Exception('invalidClone');
    }
}
以下のように使います:
$pdo = saso\classes\base\PdoManager::getPdo();
$pdo->query('内容');

2013年10月18日金曜日

在庫管理システム『SASO1.0』のディレクトリ構造

下記のツリーは作成した在庫管理システムのディレクトリ構造です。
MVCアーキテクチャーで作りました。

まず、index.phpではオートローダと振り分けクラスを呼び出します。
全てのアクセスが、index.phpを経由します。
なお、config.phpも呼び出しますがそれは単に定数定義です。

classesディレクトリの中身は三つです。
baseはPDOやリクエスト振り分けなど、ほぼすべての箇所で使いまわすクラスを収めたディレクトリです。
controllersはMVCのC、そのままです。modelsはMです。

extentionは自作でないソフトの置き場です。
バーコードラベル印刷に使うPDFの出力を行うtcpdfをおきました。
http://www.tcpdf.org/(本家。ダウンロードできます)
http://www.monzen.org/doc/tcpdf/jman/(マニュアル日本語訳)

imgは画像ファイル、styleはスタイルシートです。

logicにはクラスを使うレガシーPHPのファイルを入れます。
viewはMVCのVです。ただしテンプレートシステムは使いませんでした。
このlogicとviewの中身のディレクトリは全て、コントローラクラスにそのまま対応します。
例えば、imageディレクトリの中身はImageController.phpから呼び出されます。
さらに各ディレクトリの中身のファイルがコントローラクラスのメソッドに対応します。
例えば、ImageController.phpのaddImageAction()はimageディレクトリのaddImage.phpを呼び出します。

saso1_0
├── .htaccess
├── index.php
├── config.php
├── autoload.php
├── classes
│   ├── base
│   ├── controllers
│   └── models
├── extention
│   └── tcpdf
├── img
├── logic
│   ├── image
│   ├── item
│   ├── label
│   ├── quantity
│   ├── shelf
│   └── start
├── style
└── view
    ├── image
    ├── item
    ├── label
    ├── quantity
    ├── shelf
    └── start

.htaccessの中身は以下の通りです:

RewriteEngine On
RewriteBase /saso1_0/
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule !\.(pdf|ico|gif|jpg|png|css|html|xml)$ index.php

こう書いておくと、最後の行で指定した拡張子以外のファイルをどう呼びだそうと、
index.phpに飛ばされます。そして、そのindex.phpはアドレスバーに表示されません。

2013年8月28日水曜日

配列をforeachで回すときに最後を検出する方法

配列をforeachで回すときに、最後を検出する方法の簡潔なやつを思いついたのでメモしておきます。
例えば、商品のサイズを配列に格納し、
「Sサイズ、Mサイズ、Lサイズ」
のように、表記したいとします。
「Sサイズ、Mサイズ、Lサイズ、」
のように最後に「、」が入るとかっこ悪いので、これを消したいです。

<?php
$size = array('Sサイズ', 'Mサイズ', 'Lサイズ');
foreach($size as $key => $value){
    echo $value;
    if(count($size)-1 != $key ){
        echo "、";
    }
}
?>

最後だけなにかしたい場合は

<?php
$size = array('Sサイズ', 'Mサイズ', 'Lサイズ');
foreach($size as $key => $value){
    echo $value;
    if(count($size)-1 == $key ){
        echo "。";
    }
}
?>

とすればOKです。

2013年8月8日木曜日

「static::」キーワードの使い方

在庫管理システムをPHPで作っています。

静的プロパティや静的メソッドを参照するための、
「parent::」や「self::」は理解しやすいと思います。
一方「static::」はマニュアルを読んでもとてもややこしいものです。
しかし、どうやら使いどころは簡単です。

結論としては、子クラスから呼ばれた親クラスが、子クラスの静的プロパティや静的メソッドを参照したい時に使うようです。

親クラスを直接インスタンス化すれば「static::」は「self::」と同義になります。
親クラスが抽象クラスなら、「static::」は必ず子クラスの静的プロパティや静的メソッドを呼ぶことになります。

class Tomjr extends Tom {
    protected static $sentence="私はTomjr";
    public function foo(){
        $this->bar();
    }
}

class Tom {
    protected static $sentence="私はTom";
    public function bar(){
        echo static::$sentence;//親クラスが子クラスの静的プロパティ・メンバを呼ぶときに使う
    }
}

$tomjr = new Tomjr();
$tomjr->foo();//結果:私はTomjr
$tom = new Tom();
$tom->bar();//結果:私はTom

なお、子クラスが該当の静的プロパティ・メンバを持っていなかっ場合は、親クラスのそれが呼ばれます。

本質的に理解するには以下をご参考下さい。
図を使った簡潔でわかりやすい説明がありました:

PHPを愛する試み ~self:: parent:: static:: および遅延静的束縛~