Let’s compose and play another song with our script that we have wrote in the previous post.

$anotherSong = new Song(['Sol', 'Sol', 'Sol', 'Mi', 'Fa', 'Fa', 'Fa', 'Re']);
$guitar->play($song);

It sounds the same song as the first one. We can compare them by the operator ==.

if ($song == $anotherSong) {
	echo 'The two songs are the same' . PHP_EOL;
} else {
	echo 'The two songs are not the same' . PHP_EOL;
}

When we run the script we will realize that the two Songs are the same. Now let’s compose a third Song.

$beethovenFifthSymphony = new Song(['G', 'G', 'G', 'E', 'F', 'F', 'F', 'D']);

Now we want to compare the third Song also. Since we don’t want to repeat ourselves, we can write a printWhetherSongsAreEqual function.

function printWhetherSongsAreEqual(Song $song, Song $anotherSong)
{
	if ($song == $anotherSong) {
		echo 'The two songs are the same' . PHP_EOL;
	} else {
		echo 'The two songs are not the same' . PHP_EOL;
	}
}

And then use that function.

$notes = ['Sol', 'Sol', 'Sol', 'Mi', 'Fa', 'Fa', 'Fa', 'Re'];
$song = new Song($notes);

$anotherSong = new Song(['Sol', 'Sol', 'Sol', 'Mi', 'Fa', 'Fa', 'Fa', 'Re']);
echo 'Is $song equal with $anotherSong?' . PHP_EOL;
printWhetherSongsAreEqual($song, $anotherSong);

$beethovenFifthSymphony = new Song(['G', 'G', 'G', 'E', 'F', 'F', 'F', 'D']);
echo 'Is $song equal with $beethovenFifthSympony?' . PHP_EOL;
printWhetherSongsAreEqual($song, $beethovenFifthSymphony);

When we run the script we will see the following.

[savvas@localhost play_guitar_with_php]$ php play_guitar_second.php
Is $song equal with $anotherSong?
The two songs are the same.
Is $song equal with $beethovenFifthSympony?
The two songs are NOT the same.

But it sounds the same song again. Actually is the same with different notation. The first two are with the Romance notation and the latter one with the English notation. But PHP does not know that. PHP compare oparator is comparing the array whether it has the same strings. We need to define the Notes model, and encapsulate this knowledge inside the class. Let’s start by creating a Note class.

class Note
{
	public $note;

	public function __construct(string $note)
	{
		$this->note = $note;
	}
}

Is that a good class for the Note?

What if we compare two notes var_dump(new Note('Sol') == new Note('G') by using var_dump? We will see that this comparison returns false. Obviously because PHP is comparing again strings. One possible solution is to keep in the internal object one of the two notations and on the construction of the Note, map to the internal notation. But we need two constructors, one that will construct the Note fromRomanceNotation and one that will construct a Note object fromEnglishNotation. English notation looks simpler, since it’s one character so let’s keep this one and at the Romance Notation constructor we will map the notes to the English notation.

class Note
{
    const FROM_ROMANCE_TO_ENGLISH = [
        'Do' => 'C',
        'Re' => 'D',
        'Mi' => 'E',
        'Fa' => 'F',
        'Sol' => 'G',
        'La' => 'A',
        'Si' => 'B'
    ];

    public $note;

    public function __construct(string $note)
    {
        $this->note = $note;
    }

    public static function fromRomanceNotation(string $romanceNotationNote)
    {
        return new static(self::FROM_ROMANCE_TO_ENGLISH[$romanceNotationNote]);
    }

    public static function fromEnglishNotation(string $englishNotationNote)
    {
        return new static($englishNotationNote);
    }
}

Note that we have used the static keyword. Functions that are declared as statics can be called without the need to instantiate an object first.

The class Note now has the flexibility to be constructed from both notations. But some weaknesses still in the class. We can use the Note class in a way that will cause inconsistencies.

$do = new Note('Do');
// The internal notation is the English, but we parsed to it Romance notation
$do->note = 'Re';
// We are assigning directly to the internal property the note.
$re = Note::fromRomanceNotation('D');
// We are using the English notation to create from Romance notation.
$mi = Note::fromEnglishNotation('mi');
// And the opposite.

To prevent those issues we will do the followings

  1. Switch the visibility of the constructor to private so only the internal methods of the class will be able to use it.
  2. Switch the visibility of the property $note to private, so it can be change only from the class.
  3. Allow only the romance notation strings for the Romance Notation constructor and the English notation for the English Notation constructor.
class Note
{
    const ENGLISH_NOTATION_NOTES = [
        'C', 'D', 'E', 'F', 'G', 'A', 'B'
    ];

    const ROMANCE_NOTATION_NOTES = [
        'Do', 'Re', 'Mi', 'Fa', 'Sol', 'La', 'Si'
    ];

    const FROM_ROMANCE_TO_ENGLISH = [
        'Do' => 'C',
        'Re' => 'D',
        'Mi' => 'E',
        'Fa' => 'F',
        'Sol' => 'G',
        'La' => 'A',
        'Si' => 'B'
    ];

    private $note;

    private function __construct(string $note)
    {
        $this->note = $note;
    }

    public static function fromRomanceNotation(string $romanceNotationNote)
    {
        if (!in_array($romanceNotationNote, self::ROMANCE_NOTATION_NOTES)) {
            throw new \Exception('Invalid arguments for the Romance Notation constructor.');
        }
        return new static(self::FROM_ROMANCE_TO_ENGLISH[$romanceNotationNote]);
    }

    public static function fromEnglishNotation(string $englishNotationNote)
    {
        if (!in_array($englishNotationNote, self::ENGLISH_NOTATION_NOTES)) {
            throw new \Exception('Invalid arguments for the English Notation constructor.');
        }

        return new static($englishNotationNote);
    }
}

Note here that we have used constants, exceptions and the in_array function.

Now we can improve also the Song class, so we will be able to construct songs from array of both notations.

class Song
{
    private $notes = [];

    private function __construct(array $notes)
    {
		$this->notes = $notes;
    }

    public static function fromEnglishNotation(array $arrayEnglishNotationNotes)
    {
        $arrayNotes = [];
        foreach($arrayEnglishNotationNotes as $stringEnglishNotationNote) {
            $arrayNotes[] = Note::fromEnglishNotationString($stringEnglishNotationNote);
        }
        return new static($arrayNotes);
    }

    public static function fromRomanceNotation(array $arrayRomanceNotationNotes)
    {
        $arrayNotes = [];
        foreach($arrayRomanceNotationNotes as $stringRomanceNotationNote) {
            $arrayNotes[] = Note::fromRomanceNotationString($stringRomanceNotationNote);
        }
        return new static($arrayNotes);
    }
}

And then rewrite our test script.

$notes = ['Sol', 'Sol', 'Sol', 'Mi', 'Fa', 'Fa', 'Fa', 'Re'];
$song = Song::fromRomanceNotation($notes);

$anotherSong = Song::fromRomanceNotation(['Sol', 'Sol', 'Sol', 'Mi', 'Fa', 'Fa', 'Fa', 'Re']);
echo 'Is $song equal with $anotherSong?' . PHP_EOL;
printWhetherSongsAreEqual($song, $anotherSong);

$beethovenFifthSymphony = Song::fromEnglishNotation(['G', 'G', 'G', 'E', 'F', 'F', 'F', 'D']);
echo 'Is $song equal with $beethovenFifthSympony?' . PHP_EOL;
printWhetherSongsAreEqual($song, $beethovenFifthSymphony);

When we run again the script, on both cases the songs will be equal.

[savvas@localhost play_guitar_with_php]$ php play_guitar_second.php
Is $song equal with $anotherSong?
The two songs are the same.
Is $song equal with $beethovenFifthSympony?
The two songs are the same.

That’s all for today.

Copy the Script 🔓 and Play with it ⛹.

Be Creative 🎨 and Send me an Email 📧.