Object

物件

2019/10/16 (增補內容)

基本概念

這部分跟java滿像的 (詳參: Object Oriented Programming in PHPPHP - What is OOP?)

只是java用「.」而php用「->」。因為「.」已經用在字串相加了。就如同在java裡一樣,可以定義這些變數(屬性)為private,當定義為private時,就不能直接去存取這些變數,只能透過function(java裡的method)存取這些變數(詳參: PHP OOP - Access Modifiers),這些function通常被稱為getter及setter。這樣的作法就稱為封裝(Encapsulation)。方法中如果要取得這些變數,跟java一樣,就需要用this。

在php裡,建構式的宣告是用__construct,注意,是兩個底線。(詳參: PHP - The __construct Function)

   function __construct($name, $city, $rank){
      $this->name = $name;
      $this->city = $city;
      $this->rank = $rank;
   }

customer.php (詳參: PHP OOP - Classes and Objects)

<?php

class Customer {
   /* Member variables */
   /* make them private for encapsulation */
   private $name;
   private $city;
   private $rank;

   /* constructor */
   function __construct($name, $city, $rank){
      $this->name = $name;
      $this->city = $city;
      $this->rank = $rank;
   }

   /* Member functions */
   function setName($name){
      $this->name = $name;
   }
   
   function getName(){
      return $this->name;
   }
   
   function setCity($city){
      $this->city = $city;
   }
   
   function getCity(){
      return $this->city;
   }

   function setRank($rank){
      $this->rank = $rank;
   }
 
   function getRank(){
      return $this->rank;
   }

}

?>

物件陣列

在php裡,定義一個陣列

$customerList = array();

也可以在定義的時候就先定義陣列裡的內容:

$customerList = array(
  new Customer("Ben","Taipei",3),
  new Customer("Mary","Taipei",2),
  new Customer("Tom","Tainan",1)
);

新增內容到陣列裡:

array_push($customerList, new Customer("Rich","Yilan",2));

列出所有陣列裡的內容:

foreach( $customerList as $customer ) {
  echo "Name is ".$customer->getName()." <br />";
  echo "City is ".$customer->getCity()." <br />";
  echo "Rank is ".$customer->getRank()." <br />";
}

產生一個Customer的陣列,並印出陣列內容的完整程式。

php裡有個特別的概念,include/require,其實就等於是把被include/require放到這個程式裡,這樣可以重複利用某一個部分的程式,例如,customer.php。

customerApp.php

<?php
require 'customer.php';
$customerList = array(
  new Customer("Ben","Taipei",3),
  new Customer("Mary","Taipei",2),
  new Customer("Tom","Tainan",1)
);
array_push($customerList, new Customer("Rich","Yilan",2));

foreach( $customerList as $customer ) {
  echo "Name is ".$customer->getName()." <br />";
  echo "City is ".$customer->getCity()." <br />";
  echo "Rank is ".$customer->getRank()." <br />";
}
?>

Magic Methods

可以利用php的magic methods,這樣就可以保有encapsulation,又可以不必寫一堆setter跟getter。(詳參: What are PHP Magic Methods?16 Magic Methods That PHP Developers Must Know)

customer.php

<?php

class Customer {
  /* Member variables */
  /* make them private for encapsulation */
  private $name;
  private $city;
  private $rank;

  /* constructor */
  function __construct($name, $city, $rank){
    $this->name = $name;
    $this->city = $city;
    $this->rank = $rank;
  }

  //https://culttt.com/2014/04/16/php-magic-methods/
  //https://www.tutorialdocs.com/article/16-php-magic-methods.html

  //only private variables were set
  function __set($variable, $value){}
   
  function __get($variable){  
    return $this->$variable;
  }
}

?>

使用的時候,因為已經沒有定義getter及setter,所以,就直接存取private變數

customerApp.php

<?php
require 'customer.php';
$customerList = array(
  new Customer("Ben","Tainan",3),
  new Customer("Tom","Taipei",2),
  new Customer("Mary","Tainan",1)
);
array_push($customerList, new Customer("Rich","Yilan",2));

foreach( $customerList as $customer ) {
  echo "Name is ".$customer->name." <br />";
  echo "City is ".$customer->city." <br />";
  echo "Rank is ".$customer->rank." <br />";}
?>

直接存取時,不就違反encapsulation了嗎? 跟直接讓別人存取這些變數不同的是,當我們要控制變數的內容時,可以利用__set及__get來控制,這樣的話,即使可以直接存取變數,也都會被迫透過__set及__get來存取這些變數,完全達到encapsulation的目的。例如,我們要控制rank一定要大於等於0。

<?php

class Customer {
  /* Member variables */
  /* make them private for encapsulation */
  private $name;
  private $city;
  private $rank;

  /* constructor */
  function __construct($name, $city, $rank){
    $this->name = $name;
    $this->city = $city;
    $this->setRank($rank);
  }

  //https://culttt.com/2014/04/16/php-magic-methods/
  //https://www.tutorialdocs.com/article/16-php-magic-methods.html

  //only private variables were set
  function __set($variable, $value){
    if ($variable == "rank") {
      $this->setRank($value);
    } else {
      $this->$variable = $value;
    }
  }
   
  function __get($variable){
    return $this->$variable;
  }

  function setRank($value){
    if ($value < 0 ) {
      $this->rank = 0;
    } else {
        $this->rank = $value;
    }

  }

}

?>

排序

如果我們直接對一個物件陣列排序,php會以第一個欄位來作為排序的依據: (PHP sort() Function)

<?php
function printAll($customerList){
  
 foreach( $customerList as $customer ) {
   echo "Name is ".$customer->name." <br />";
   echo "City is ".$customer->city." <br />";
   echo "Rank is ".$customer->rank." <br />";}
 
}

require 'customer.php';
$customerList = array(
  new Customer("Ben","Tainan",3),
  new Customer("Tom","Taipei",2),
  new Customer("Mary","Tainan",1)
);
array_push($customerList, new Customer("Rich","Yilan",2));

printAll($customerList);
echo "------";

sort($customerList);

printAll($customerList);
echo "------";
?>

結果是:

Name is Ben
City is Tainan
Rank is 3
Name is Tom
City is Taipei
Rank is 2
Name is Mary
City is Tainan
Rank is 1
Name is Rich
City is Yilan
Rank is 2

Name is Ben
City is Tainan
Rank is 3
Name is Mary
City is Tainan
Rank is 1
Name is Rich
City is Yilan
Rank is 2
Name is Tom
City is Taipei
Rank is 2

如果要以其他規則來排序,就必須使用usort (PHP usort() Function / Sort array of objects by object fields in PHP),usort的第二個參數是排序依據的function名稱 (byCity)。

usort($customerList, "byCity");

排序的依據就是回傳一個判斷大小的依據

function byCity($object1, $object2){
  return $object1->city > $object2->city; 
}

也可以是比較複雜的比較,例如,如果city的內容相同就以name大小排序,如果city的內容不相同就以city大小排序:

function byCity($object1, $object2){
 if ($object1->city == $object2->city){
  return $object1->name > $object2->name; 
 }
 else {
  return $object1->city > $object2->city; 
 }
 
}

完整的程式:

<?php
function printAll($customerList){
  
 foreach( $customerList as $customer ) {
   echo "Name is ".$customer->name." <br />";
   echo "City is ".$customer->city." <br />";
   echo "Rank is ".$customer->rank." <br />";}
 
}
function byCity($object1, $object2){
 if ($object1->city == $object2->city){
  return $object1->name > $object2->name; 
 }
 else {
  return $object1->city > $object2->city; 
 }
 
}

require 'customer.php';
$customerList = array(
  new Customer("Mary","Tainan",3),
  new Customer("Tom","Taipei",2),
  new Customer("Ben","Tainan",1)
);
array_push($customerList, new Customer("Rich","Yilan",2));

printAll($customerList);
echo "<br/>";

sort($customerList);

printAll($customerList);
echo "<br/>";

usort($customerList, "byCity");

printAll($customerList);
echo "<br/>";
?>

結果如下:

Name is Mary
City is Tainan
Rank is 3
Name is Tom
City is Taipei
Rank is 2
Name is Ben
City is Tainan
Rank is 1
Name is Rich
City is Yilan
Rank is 2

Name is Ben
City is Tainan
Rank is 1
Name is Mary
City is Tainan
Rank is 3
Name is Rich
City is Yilan
Rank is 2
Name is Tom
City is Taipei
Rank is 2

Name is Ben
City is Tainan
Rank is 1
Name is Mary
City is Tainan
Rank is 3
Name is Tom
City is Taipei
Rank is 2
Name is Rich
City is Yilan
Rank is 2

簡易購物車

product.php

<?php

class Product {
  /* Member variables */
  /* make them private for encapsulation */
  
  private $id;
  private $name;
  private $price;

  /* constructor */
  function __construct($id, $name, $price){
    $this->id = $id;
    $this->name = $name;
    $this->setPrice($price);
  }

  //https://culttt.com/2014/04/16/php-magic-methods/
  //https://www.tutorialdocs.com/article/16-php-magic-methods.html

  //only private variables were set
  function __set($variable, $value){
    if ($variable == "price") {
      $this->setPrice($value);
    } else {
      $this->$variable = $value;
    }
  }
   
  function __get($variable){
    return $this->$variable;
  }

  function setPrice($value){
    if ($value < 0 ) {
      $this->price = 0;
    } else {
        $this->price = $value;
    }

  }
}

?>

productList.php

使用ProductData儲存產品資料,點選產品可以看到產品的內容並輸入購買數量 (productShow.php)。

<?php
require 'productData.php';
$productData = new ProductData();
$productList = $productData->listAllProducts();
?>
<html>

<head>
  <meta charset="utf-8">
</head>

<body>
 <table>
  <tr>
   <th>產品編號</th>
   <th>產品名稱</th>
   <th>價格</th>
   <th></th>
  </tr>
  <?php
  foreach( $productList as $product ) {
  ?>
  <tr>
   <td><?=$product->id?></td>
   <td><?=$product->name?></td>
   <td><?=$product->price?></td>
   <td><a href="productShow.php?id=<?=$product->id?>">看內容</a></td>
  </tr>
  <?php
  }
    ?>

 </table>


</body>

</html>

productData.php

將產品資料儲存在陣列中,未來資料儲存在資料庫中。

<?php
require 'product.php';
class ProductData{
  /* Member variables */
  /* make them private for encapsulation */
  private $products;

  /* constructor */
  function __construct(){
    $this->products= array(
      new Product("0","iPhone 11",24900),
      new Product("1","iPhone 11 Pro",35900),
      new Product("2","iPhone 11 Pro Max",39900),
      new Product("3","iPhone XR",21500),
      new Product("4","iPhone 8",15900),
      new Product("5","iPhone 8 Plus",19900)
    );
  }

  function listAllProducts(){
    return $this->products;
  }
 
  function retrieveProduct($id){
    return $this->products[$id];
  }

}
?>

productShow.php

利用ProductData取得產品資料,輸入數量後呼叫cardAdd.php,將資料放進session裡。

<?php
require 'productData.php';
$id = $_GET["id"] ?? "0";
$productData = new ProductData();
$product = $productData->retrieveProduct($id);
?>
<html>

<head>
  <meta charset="utf-8">
</head>

<body>
 <form action="cartAdd.php" method="post">
  產品編號: <?=$product->id?><br>
  <input type="hidden" name="id" value="<?=$product->id?>">
  產品名稱: <?=$product->name?><br>
  價格: <?=$product->price?><br>
  數量: <input type="text" name="qty"><br>
  <input type="submit" value="Add">
 </form>

</body>

</html>

cart.php

<?php

class Cart {
  /* Member variables */
  /* make them private for encapsulation */
  
  //these will be stored in session
  private $id;
  private $qty; //quantity

  //for cartList only
  private $product;

  /* constructor */
  function __construct($id, $qty, $product=null){
    $this->id = $id;
    $this->setQty($qty);
  $this->product = $product; 
  }

 //https://culttt.com/2014/04/16/php-magic-methods/
  //https://www.tutorialdocs.com/article/16-php-magic-methods.html

  //only private variables were set
  function __set($variable, $value){
    if ($variable == "qty") {
      $this->setQty($value);
    } else {
      $this->$variable = $value;
    }
  }
   
  function __get($variable){
    return $this->$variable;
  }

  function setQty($value){
    if ($value < 1 ) {
      $this->qty = 1;
    } else {
        $this->qty = $value;
    }

  }
}

?>

cardAdd.php,將資料放進session後,呼叫cartList。

<?php
require 'cart.php';
session_start();
$id = $_POST["id"] ?? "0";
$qty = $_POST["qty"] ?? "1";
//initialize session for first item
if (!isset($_SESSION["cart"])){
  $_SESSION["cart"]=[];
}

array_push($_SESSION["cart"], new Cart($id,$qty));

header("Location: cartList.php");
?>

cartList.php,顯示的時候,利用ProductData取得產品資料,cartClear.php會清空購物車。

<?php
require 'cart.php';
require 'productData.php';
$productData = new ProductData();
session_start();
//prevent error when session does not exist
$shoppingCart = $_SESSION["cart"] ?? [];
//initialize an empty list
$shoppingList = [];
foreach( $shoppingCart as $item ) {
  
 $product = $productData->retrieveProduct($item->id);
 //combine cart with product
 $fullProduct = new Cart($item->id, $item->qty, $product);
 array_push($shoppingList, $fullProduct);
}


?>
<html>

<head>
 <meta charset="utf-8">
</head>
<a href="cartClear.php">清除購物車</a><br>
<a href="productList.php">繼續購物</a><br>
 
<body>
 <table>
  <tr>
   <th>產品編號</th>
   <th>產品名稱</th>
   <th>價格</th>
   <th>數量</th>
  </tr>
  <?php
  foreach( $shoppingList as $item ) {
  ?>
  <tr>
   <td><?=$item->id?></td>
   <td><?=$item->product->name?></td>
   <td><?=$item->product->price?></td>
   <td><?=$item->qty?></td>
  </tr>
  <?php
  }
  ?>

 </table>


</body>

</html>

cartClear.php 清空購物車,回到cartList。

<?php
session_start();
unset($_SESSION["cart"]);
header("Location: cartList.php");
?>

** 作業 **

  • 利用產品與購物車的範例,新增登入介面(請利用session作業的登入),沒登入只能看到產品,無法購物,登入後才能將產品置入購物車。
  • 登入後,可以選擇讓產品資料依name由小到大排序。(挑戰題)
  • 登入後,可以選擇讓產品資料依price由小到大排序。(挑戰題)

參考資料