web-dev-qa-db-ja.com

OOPの概念を適用して、シンプルだが実際のWebアプリケーションを構築する方法を教えてください。

私は長い間、OOPに頭を抱えようとしてきました。その利点がわかります。私はたくさんのチュートリアルを読んだり、テーマに関する同数のビデオを見たりしました。動物/猫/犬の例、車/ドライブの例を取得します。私が苦労しているのは、これらの概念を実際のアプリケーションに適用する方法です。そこで、私はOOPを使用してビルドすることにしました。

構文のヘルプや特定のコードの記述を求めているのではありません。ドキュメントやフォーラムを検索することで自分自身を見つけることができます。本当に必要なのは、いくつかのガイダンスと、時々正しい方向へのプッシュです。私をメンターしてくれる経験豊富なプログラマーはいますか?

私の学習プロジェクトとして、簡単なクラシファイド広告の「Webアプリ」を構築したいと考えています。 Craigslistに似たものですが、範囲の観点からすっきりしています。 PHP5とMySQLは使い慣れているので、使用したいと思います。

次の2つの使用例しかないとしましょう。

  1. 販売用に何かを投稿する
  2. 購入する商品の閲覧/検索

オブジェクトとすべき「もの」は何か?各アイテムがオブジェクトであると想像できますが、どの時点でですか?なぜ?

したがって、たとえば、ユーザーは「販売アイテムの投稿」フォームに記入します。そのフォームを、データベースに値を挿入する別のオブジェクトに渡されるオブジェクトに変換する必要がありますか?

別のユーザーが閲覧していて、カテゴリCのすべてのアイテムの表示を要求している場合はどうなりますか?アプリがデータベースに接続する必要がある場合は常に、データベースオブジェクトを作成し、一連の項目オブジェクトを取得してページに表示することは理にかなっていますか? …これを書いても、私がOOPについていまだに無知であることに気付くでしょう。修正を手伝ってください。

あなたの意見では、これがOOPに参加するのに適したプロジェクトではない場合は、別のアイデアを提案してください!

25
bernk

ここのアドバイスは、これまでのところ、新しいOO学習者にとってはひどいものだと思います。オブジェクトを、以下で定義された「もの」の特定のインスタンスの表現として考えることは、あまりお勧めできません。いくつかのクラスです。互いに相互作用するが、お互いの内部構造は持たない、マシンのコンパートメント化されたコンポーネントと考えるのがよいでしょう。これらの各コンポーネントは状態を維持します

ORM(object-relational-mapping)をDBインタラクションに使用する場合、使用または作成するフレームワークには、おそらく「もの」のコレクションであるテーブルを表すいくつかの浅いオブジェクトが含まれますが、私はORMを個人的に好きではありません、そしてそれらは必ずしも理想的なOOプラクティスを表すとは限りませんが、大規模なWebアプリで人気があります。

その上、おそらく、1つ以上のDB接続など、Webアプリマシンで実行する必要があるいくつかの重要なコンポーネントがあります(接続を維持するクラスを作成して、準備されたクエリを実行できます-PDOは、所有しますが、私はそれをラップします)、そしておそらくあなたのビューのテンプレートシステムコントローラーをPHPオブジェクトにすることもできます。フォームに入力する場合、CSRF保護であるP/R/Gのフォーム値を維持するオブジェクトがあるかもしれません。トークン、およびその入力の検証を実行できます。

Webアプリのデザインとオブジェクトグラフを作成するときに、オブジェクトに変わる「もの」を探してはいけません。代わりに、一緒になってそれを作成する論理コンポーネントについて考える必要があります。あなたがこれを強制しようとするべきではないと思います、そしてそれはかなり自然に来るはずですが、正しく行うことは非常に難しく、間違いなく途中でいくつかの設計上の決定を変更しなければならなくなります。

私の最後のアドバイスはこれです:継承よりも構成が進むべき道です。

17
Explosion Pills

OOPを使用してペットを売買する方法は次のとおりです。同じ方法で車や飛行機を売ることができます。; p

<?php
// define a superclass .. no instances will be made of 'animal' itself,
// but it is useful to define common characteristics and behaviours
// (ie: properties and methods) of all our classes of animals
class Animal {

    // this constructor function is called whenever a new instance
    // of the Animal class is created (or any class that inherits from Animal)
    function Animal ($colour) {

        // install the argument as an attribute of any instances of Animal
        $this->colour = $colour;
    }

    // this method will be available to all classes that inherit from Animal
    function report () {
        return "This ".$this->colour." ".get_class($this)." has ".$this->legs." legs.<br />";
    }
}

// this class inherits from Animal
class Cat extends Animal {

    // set the legs attribute
    public $legs = 4;

    // create a method that can be called from any instances of Cat
    function make_noise () {
        echo "MEOW!<br />";
    }
}

// this class inherits from Cat, and from Animal
class Panther extends Cat {

    // specifies the colour attribute
    public $colour = "black";

    // overwrites the constructor function that would otherwise be
    // inherited from Animal, with a blank constructor.
    function Panther () {}

    // overwrites the method inherited from Cat
    function make_noise () {
        echo "ROARRRR!<br />";
    }
}

// this class inherits from Animal
class Snake extends Animal {
    public $legs = 0;
}

// this class is unrelated to the others
class PetShop {

    // set up an array to store the pets that the shop will stock
    public $pets = array ();

    // set up a variable to store the total cash in the pet shop
    public $cash;

    // this method creates a new object and adds it to the pets array
    function add_pet ($petclass, $price, $colour) {

        // set up a variable containing the number of elements in the pets array
        $n_pets = count($this->pets);

        // add to the pets array, a new instance of the class specified as
        // the first argument in this method, using the last argument as the
        // colour argument that is passed to the specified class's constructor
        $this->pets[$n_pets] = new $petclass($colour);

        // add a 'price' attribute to the pet object
        $this->pets[$n_pets]->price = $price;
    }

    // this method removes the specified pet from the array and adds the price
    // to the pet shop's cash variable
    function sell_pet ($n) {

        // add pet's price to the cash total
        $this->cash += $this->pets[$n]->price;

        // remove the pet object from the array
        array_splice($this->pets, $n, 1);

        // give a message about the sale
        echo "SALE: Pet no. ".$n." sold. Total cash is now \$".$this->cash.".<br /><br />";
    }

    // this method reports on the pet shop's stock
    function show_pets () {

        // show the number of pets available
        echo "<B>Shop stock:</B><br />We have ".count($this->pets)." pets for sale.";
        echo "<br /><br />";

        // iterate through the pets array and show information about each one
        for ($i = 0; $i < count($this->pets); $i++) {
            echo "<B>Pet No. ".$i.": </b>".$this->pets[$i]->report();
            echo "Price: \$".$this->pets[$i]->price."<br />";
        }
        echo "<br />";
    }
}

// instantiate a new PetShop object
$shop = new PetShop ();

// add three pets to the shop
$shop->add_pet(cat, 20, "tabby");
$shop->add_pet(snake, 40, "brown");
$shop->add_pet(snake, 60, "black");

// show the pet's stock
$shop->show_pets();

// sell the first pet in the stock
$shop->sell_pet(0);

// show the pet's stock after the sale
$shop->show_pets();
?>
9

OPのリクエストに応じて、ゲストブックコードを共有します。
メッセージクラス:

<?php 
Class message
{
    private $db;
    private $messageID;
    private $message;
    private $name;
    private $mail;

    public function setmessageID($messageID)
    {
        $this->messageID = $messageID;
    }

    public function getmessageID()
    {
        return $this->messageID;
    }

    public function setmessage($message)
    {
        $this->message = $message;
    }

    public function getmessage()
    {
        return $this->message;
    }

    public function setname($name)
    {
        $this->name = $name;
    }

    public function getname()
    {
        return $this->name;
    }

    public function setMail($mail)
    {
        $this->mail = $mail;
    }

    public function getMail()
    {
        return $this->mail;
    }
}

メッセージデータアクセスオブジェクトクラス:

<?php 
class messageDAO
{
    private $db;
    private $aantalMessages;
    private $messages;
    private $message;

    //bij laden roept hij automatisch Db class aan (en de daarbij gezeten functies)
    public function __construct(Db $db)
    {
        $this->db = $db;
    }

    public function getMessages()
    {
        return $this->messages;
    }

    public function getAantalMessages()
    {
        return $this->aantalMessages;
    }

    //Function to retrieve messages
    public function findMessages($args)
    {       
        $dbh = $this->db->DBH();

        //$offset for pagination
        $offset = ($args['currentPage'] - 1) * $args['itemsPerPage'];

        $sth = $dbh->prepare("SELECT    SQL_CALC_FOUND_ROWS
                                                    messageen.messageID, 
                                                    messageen.message, 
                                                    messageen.name, 
                                                    messageen.mail
                                            FROM    `messageen` 
                                            ORDER BY messageen.datumToegevoegd DESC 
                                            LIMIT   ?, ?");
        $sth->bindParam(1, $offset, PDO::PARAM_INT);
        $sth->bindParam(2, $args['itemsPerPage'], PDO::PARAM_INT);
        $sth->execute();
        $sth->setFetchMode(PDO::FETCH_ASSOC);

        $messages = array();

        while($row = $sth->fetch())
        {
            $message = new message();
            $message->setMessageID(htmlentities(strip_tags($row['messageID'])));
            $message->setSessage(htmlentities(strip_tags($row['message'])));
            $message->setName(htmlentities(strip_tags($row['name'])));
            $message->setMail(htmlentities(strip_tags($row['mail'])));  
            $messages[] = $message; 
        }

        $sth = $dbh->prepare("SELECT FOUND_ROWS() as numberOfMessages");
        $sth->execute();
        $sth->setFetchMode(PDO::FETCH_ASSOC);
        $this->numberOfMessages = $sth->fetch();

        return $messages;
    }

    public function setMessageToEdit($args)
    {   
        $sth = $this->db->DBH()->prepare("SELECT    messages.message
                                            FROM    `messages`
                                            WHERE   messages.messageID = ?");
        $sth->bindParam(1, $args['messageID']);
        $sth->execute();
        $sth->setFetchMode(PDO::FETCH_ASSOC);
        //return the retrieved message
        while($row = $sth->fetch())
        {
            $message = new message();
            $message->setMessage(htmlentities(strip_tags($row['message'])));
            $message->setMessageID(intval($args['messageID']));
        }

        return $message;
    }

    //functie om messageen aan te passen
    public function save(message $message)
    {   
        //insert part
        //if(isset($message->getname()) && isset($message->getmessage()) && isset($message->getMail()))
        //{
            $sth = $this->db->DBH()->prepare("INSERT INTO   `messages`
                                                    SET     messages.name = ?,
                                                            messages.mail = ?,
                                                            messages.message = ?,
                                                            messages.dateAdded = NOW()");
            $sth->bindParam(1, $message->getName());
            $sth->bindParam(2, $message->getMail());
            $sth->bindParam(3, $message->getMessage());
            $sth->execute();
        //}

        //update part       
        /*if(isset($message->getmessageID()) && isset($message->getmessage()))
        {
            $sth = $this->db->DBH()->prepare("UPDATE    `messageen`
                                                SET     messageen.message = ? 
                                                WHERE   messageen.messageID = ?
                                                LIMIT   1");
            $sth->bindParam(1, $message->getmessage());
            $sth->bindParam(2, $message->getmessageID());
            $sth->execute();
        }*/
    }
}

index.php

<?php
//include file loader.php
include("includes/loader.php");

$guestbook = new guestbook($db);
$user = new user($db);
$messageDAO = new messageDAO($db);

//Make a array named error
$error = array();

//Get action (login/setmessage/editmessage/deletemessage)
if(isset($_GET['action']))
{   
    switch ($_GET['action'])
    {   
        //if login submit is pressed
        case 'login':
            //Check if filled
            if(isset($_POST['username']) && isset($_POST['username']))
            {
                $error['usernameEmpty'] = (bool) !strlen(trim($_POST['username']));
                $error['passwordEmpty'] = (bool) !strlen(trim($_POST['password']));
            }

            if(in_array(1, $error))
            {
                //Assign $error to smarty
                $smarty->assign('error', $error);
            }

            else
            {
                if(isset($_POST['username']) && isset($_POST['username']))
                {
                    $user->setLoggedIn(array('username'=>$_POST['username'],
                    'password'=>$_POST['password']));

                    if($user->getLoggedIn() != true)
                    {                   
                        $smarty->assign('loggedInError', $user->getLoggedIn());
                    }
                }
            }
            break;

        //Als if "place message" is pressed
        case 'placemessage':
            //if user is not logged in
            if($user->getLoggedIn() != true)
            {
                //Controleren of message-velden wel zijn ingevuld
                $error['nameEmpty'] = (bool) !strlen(trim(htmlentities(strip_tags($_POST['messagename']))));
                $error['mailEmpty'] = (bool) !strlen(trim(htmlentities(strip_tags($_POST['messageMail']))));
                $error['messageEmpty'] = (bool) !strlen(trim(htmlentities(strip_tags(str_replace('place message...','', $_POST['messageInput'])))));

                if($error['mailEmpty'] != 1)
                {
                    $error['mailInvalid'] = !filter_input((INPUT_POST), 'messageMail', FILTER_VALIDATE_EMAIL);
                }

                if(in_array(1, $error))
                {
                    $smarty->assign('error', $error);
                }

                else
                {
                    $message = new message();

                    $message->setname($_POST['messagename']);
                    $message->setMail($_POST['messageMail']);
                    $message->setmessage($_POST['messageInput']);

                    dump($message);

                    //place message             
                    $messageDAO->save($message);
                }
            }

            //if user is logged in
            else 
            {
                //is message filled?
                $error['messageEmpty'] = (bool) !strlen(trim(htmlentities(strip_tags(str_replace('place hier uw message...','', $_POST['messageInput'])))));

                if($error['messageEmpty'] != 1)
                {   
                    $user->setUser();

                    $guestbook->placemessage(array('name'=>$user->getLoggedInUsername(), 
                    'mail'=>$user->getLoggedInUserMail(),
                    'messageInput'=>$_POST['messageInput']));
                }

                else 
                {
                    $smarty->assign('error', $error);
                }
            }
            break;

        case 'deletemessage':
            $user->setUser();

            if($user->getLoggedInUserAdmin() == 1)
            {
                if(isset($_GET['messageID']) && is_numeric($_GET['messageID']) && isset($_GET['key']))
                {
                    $guestbook->setURLKey($_GET['messageID']);

                    if($guestbook->getURLKey() == $_GET['key'])
                    {                   
                        $guestbook->verwijdermessage(array('messageID'=>$_GET['messageID']));
                    }
                }
            }
            die(header("location: /index.php"));
            break;
    }
}

if(isset($_GET['pagina']) && is_numeric($_GET['pagina']))
{

    $currentpage = $_GET['pagina'];
}

else
{
    //$currentpage is 1
    $currentpage = 1;
}

$user->setUser();

//assign var to smarty
$smarty->assign('messages', $messageDAO->findmessages(array('currentpage'=>$currentpage, 'itemsPerPagina'=>10)));
$smarty->assign('user', $user);

//Pagination

$numbermessages = $messageDAO->getnumbermessages();


$totalpages = ceil($numbermessages['numbermessages'] / 10);


if($currentpage < 1)
{
    //$currentpage is 1
    $currentpage = 1;
}


if($currentpage > $totalpages)
{

    $currentpage = $totalpages;
}

$smarty->assign('numbermessages', $messageDAO->getnumbermessages());
$smarty->assign('guestbook', $guestbook);
$smarty->assign('currentpage', $currentpage);
$smarty->assign('totalpages', $totalpages);

//display index.tpl
$smarty->display('index.tpl');

わかりやすいように変数と関数の一部の名前を変更しました(オランダ語から英語:Pに翻訳されています)。これにより、クイック置換などを行っただけで、奇妙なセンテンスが見つかることがあります。楽しんでください。また、これはコード全体ではありません。20ファイルに相当するコードを投稿することになるためです。P

5
Bono

Explosion Pillsで述べたように、複雑なアプリケーションでは、ほとんどのオブジェクトは、実際のエンティティ(搭乗券、請求書、mp3ファイルなど)ではなく、アプリケーションコンポーネント(データベース接続プール、コマンド、ハッシュマップなどのデータ構造)に関連しています。 )。この分野で繰り返し発生する多くの問題を人々が解決した方法を示す、デザインパターンに関する優れた書籍がたくさんあります。知られているGOFの本は完全ですが非常に乾燥しているため、Head First Design Patternsの方がアクセスしやすいかもしれません。

実世界の分析と設計の観点から。名詞と動詞の観点から考えると役立つことがよくあります。たとえば、ビデオ貸し出しライブラリ(これらは現在廃止されていますか?)には、以下のものがあります。

  • ビデオ
  • 借り手

動詞に関して:

  • 借入人は長い間ビデオを取り出すことができます
  • 借入人はビデオを店舗などに返却できます。

次に、これらを操作を使用してクラスに変換できます(PHPを実行してから長い時間がかかるので、避けます):

class Borrower
{
  public void borrow(Video video, int daysToBorrow)
  {
     ...
  }

  public void returnVideo(Video video, boolean calculateFine)
  {
     ...
  }
}

それには、たくさんの練習と遊びが必要です。最善の方法は、行き詰まり、失敗した設計から学ぶことです。私の意見では、OOは、生涯にわたって学習および開発を続けることができるものです(簡単ではなく、完全な解決策はありません)。優れたデザインは反復的であることが多いため、 "Craig's List" Webアプリケーションのいくつかの異なるアイデアを試してください。

3
mward

最善の方法は、アプリケーションのコアに集中する方法を見つけることです。「post」、「user」、「post :: FindByName()」、「user-> Validate()」など、心配しないでください配管について多すぎる-投稿をデータベーステーブルに接着する方法、投稿の表示を異なる検索間で一貫させる方法、および「投稿を入力」フォームをデータベースレコードに接着する方法。

幸いなことに、これを行うフレームワークはたくさんあります。 OO Webアプリケーションの主要なパラダイムは「Model-View-Controller」であり、別名 [〜#〜] mvc [〜#〜] ; PHPでは使用できる既製のMVCフレームワークがいくつかあります。

これにより、学習の必要性が高まりますが、OOと同様にMVCについても学習する必要があります-これは、OOの取り組みは、主に「ビジネスドメインを表す「モデル」レイヤー。ここでOOが最も自然で表現力があります。ほとんどのMVCフレームワークでは、「モデル」レイヤーを定義して、自動的にWebサイトを作成できます。足場と呼ばれる手法を使用してその周りに-そのようにして、すべての配管をアンピックする必要なしに、ドメインモデルのさまざまな実装を試す簡単な方法を得ることができます。

1
Neville Kuyt