Using array_rand() in PHP and I'm getting an out of range error

I’m a total new to PHP and I’m having a logic problem in my code (array_rand($arrCards,1) is running out of elements before it should, I think). So I’ve done a var_dump of the array and I get this output (brief excerpt):

    array (size=52)
  0 => 
    object(Card)[2]
      private 'suit' => string 'heart' (length=5)
      private 'rank' => string 'a' (length=1)
  1 => 
    object(Card)[3]
      private 'suit' => string 'heart' (length=5)
      private 'rank' => string '2' (length=1)
  2 => 
    object(Card)[4]
      private 'suit' => string 'heart' (length=5)
      private 'rank' => string '3' (length=1)

Here’s my Card class:

class Card {
    //properties
    private $suit;
    private $rank;

    //constructor
    public function __construct($r, $s) {
        $this->rank = $r;
        $this->suit = $s;
    }

    //methods
    public function getSuit() {
        return $this->suit;
    }

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

}

And here is how I create each card and push it into each array (this is the Deck class constructor):

//constructor
    public function __construct() {
        $this->arrCards = array();

        $objCard = new Card("a", "heart");
        array_push($this->arrCards, $objCard);
        $objCard = new Card("2", "heart");
        array_push($this->arrCards, $objCard);
        $objCard = new Card("3", "heart");
blah blah continued...

(It’s been pointed out to me that I could have constructed the Deck using two for loops, but I’ve typed it all out now so that probably doesn’t matter.)

Here’s the relevant methods in the Deck class:

//methods
    public function dealCard() {
        if ($this->hasCard()) {
            echo $this->intCount . PHP_EOL;
            $index = array_rand($this->arrCards, 1);
            $card = array_splice($this->arrCards,$index);
            return $card[0];
        } else {
            return Null;
        }
    }

    protected function hasCard() {
        if ($this->intCount > 0) {
            return true;
        } else {
            return false;
        }
    }

Here’s the error I get (preceded by the echo $this->intCount; (note how it’s not actually removing the card from the deck like I intend it to either):

52 52 52 52 
( ! ) Warning: array_rand(): Second argument has to be between 1 and the number of elements in the array in C:/wamp64/www/ofc/ofc_classes.php on line 162
Call Stack
#   Time    Memory  Function    Location
1   0.0000  240704  {main}( )   .../main.php:0
2   0.0010  348080  Deck->dealCard( )   .../main.php:11
3   0.0010  348176  array_rand ( )  .../ofc_classes.php:162

array_rand will return a random key from your input array, while
array_splice with the offset positive, will remove that many elements from the array starting from the beginning of the array.

Example:

$deck = ['A','K','Q','J','10','9','8','7','6','5','4','3','2','1'];
$card = array_rand($deck,1);

print $card . PHP_EOL;
// let's assume it outputs 9, this is the key from the array
// array_rand returns a random key from the input array

$remainingDeck = array_splice($deck,$card);
print_r($remainingDeck) . PHP_EOL;
// will output
Array
(
    [0] => 5
    [1] => 4
    [2] => 3
    [3] => 2
    [4] => 1
)

Which is not what you expect, we have eliminated 9 cards from the deck, instead of just one.

The warning you get is telling you that, after a couple of dealt cards, your deck is empty.
You can easily verify this by printing out the count of the $this->arrCards after dealing each card instead of relying on $this->intCount which does not get updated after dealing the cards.

I’d probably change the dealCard() and hasCard() methods:

public function dealCard()
{
    $result = null;
    if ($this->hasCard()) {
        $index = array_rand($this->arrCards, 1);
        $card = array_splice($this->arrCards, $index, 1);
        $result = $card[0];
    }
    return $result;
}

public function hasCard()
{
    $result = (!empty($this->arrCard));
    return $result;
}

what and why:

  • I like using $result for a method/function – you always know $result is what you are expecting to be returned
  • initialise the $result – if the code doesn’t behave as you expect it to
    you’ll always get something returned
  • use array_splice() function with the length parameter – you only need 1 card from the deck
  • 1 exit point to the method – this is a simple method, but more complicated ones with multiple exit (return) points can be difficult to debug later
  • use empty() function to check if there are more cards – it saves using/maintaining a counter of cards in the deck