Go for PHP Developers: Structs vs Classes

So unlike modern PHP, Go handles the way we interact with types in our applications in a different way, through the use of structs. In fact it tailors itself to be more primitive, like it’s grandfather C.

Go does however have similar concepts implemented, which to an outsiders point of view, could be mistaken for the same constructs which we see in PHP.

For example: Structs.

Structs in Go are a way of defining a type of data, which itself has properties. These properties have their own types, which too can be structs. There’s also a means of defining an anonymous property which can be accessed through the parent struct “inheriting” the anonymous properties data, methods etc..

Defining our Struct & Class

Let’s look at a class in PHP and then a struct in Go which implements the same features:

PHP

<?phpnamespace App;class Animal
{
public $name;
public $height;
public $weight;
public function __construct($name, $height, $weight)
{
$this->name = $name;
$this->height = $height;
$this->weight = $weight;
}
public function getInfo()
{
return [
'name' => $this->name,
'height' => $this->height,
'weight' => $this->weight,
];
}
}

Go

package maintype Animal struct {
Name string
Height uint
Weight uint
}
func NewAnimal(name string, height, weight uint) Animal {
return Animal{
Name: name,
Height: height,
Weight: weight,
}
}
func (a *Animal) GetInfo() map[string]interface{} {
return map[string]interface{}{
"name": a.Name,
"height": a.Height,
"weight": a.Weight,
}
}

Because we’re not dealing with classes in Go, there’s no constructor. So we define a function which creates a new version of the struct, assigning the parameter values that we’re passing in, the application is essentially the same, just the underlying implementation has changed.

Using them

To be honest, other than the difference in Syntax, we can treat them in a similar way. Once we’ve created a new object using the provided types, we can interact with them almost identically.

PHP

$animal = new Animal("Tiger", 100, 200);var_dump($animal->getInfo());

Go

var animal Animal
animal = NewAnimal("Tiger", 100, 200)
fmt.Println(animal.GetInfo())

You may have noticed that the function definition in the Go examples have a capitalised first letter of the name; this is to indicate that this function is public and available outside of the package; if we were to match it with the PHP example and make it lowercase it wouldn’t be available outside of the main package.

This is an automatic scoping mechanism built into Go and also works for variables defined in a package which are outside of a function; something which you wouldn’t typically find in a PHP file which contains a class, but is perfectly acceptable in Go.

Defining interfaces for our Animal

We may want to treat any struct or instance as a type contract which we can pass around, allowing us to provide multiple different implementations with the same interface.

Let’s say we know that getInfo is a defining method of our Animal interface and so we wanted anything that acted like an Animal to also provide this, we should create an interface for this and implement it in both cases. Let’s call our interfaces AnimalContract

Here’s how we do it:

In these examples we assume idiomatic approaches to packaging and code layout. In PHP it is common to make classes and interfaces their own files, making it obvious where they exist in the namespace. And for the sake of simplicity in Go, small applications tend to stick to the main package, and in this case it is assumed all code resides in a single main.go

PHP

<?phpnamespace App;interface AnimalContract
{
public function getInfo();
}

and we’ll implement it like so

<?phpnamespace App;use App\AnimalContract;class Animal implements AnimalContract;
{
// Rest of class...
}

Go

package maintype AnimalContract interface {
GetInfo() map[string]interface{}
}
type Animal struct {
// Struct def...
}
func (a *Animal) GetInfo() map[string]interface{} {
// Method body...
}

In the Go implementation we don’t need to explicitly define our use of the interface. Go is clever in that if we define a struct which defines that interface, it will implicitly inherit it.

We can now use this interface in other parts of our applications to ensure we’re receiving something which provides the definition of this interface.

Examples:

PHP

<?phpnamespace App;use App\Animal;
use App\AnimalContract;
function getAnimalInfo(AnimalContract $animal)
{
return $animal->getInfo();
}
$info = getAnimalInfo(new Animal("Tiger", 100, 200));
var_dump($info);

Here we can see that we can specify the contract type in our function definition, allowing us to pass anything that inherits this interface. Useful if you want to have flexibility and the wiggle room to swap out implementations of features without having to cause breaking changes. The adapter pattern utilises this heavily and is a simple way to make a plug & play style application, great if you’re developing an open source library or framework.

Now we’ll see that Go’s way of doing this isn’t all that different either.

Go

package maintype AnimalContract interface {
GetInfo() map[string]interface{}
}
type Animal struct {
// Struct def...
}
func (a *Animal) GetInfo() map[string]interface{} {
// Func def...
}
func GetAnimalInfo(animal AnimalContract) map[string]interface{} {
return animal.GetInfo();
}

We can see here we just need to specify the interface type name instead of our struct.

Extending Animals

We may find ourselves in a situation whereby we need all of the functionality and interfaces that our `Animal` type provides us with, BUT we’ve got a few things we need to alter for our use case. Maybe we need an extra property that’s specific to our new type, and then to go with that, a new method?

This is where class extension or embedded structs come into play. Both different implementations and concepts, but with similar use cases.

Let’s create another type which utilises the existing Animal definition, but builds upon it:

PHP

<?phpnamespace App;use App\Animal;class Tiger extends Animal
{
public function __construct($height, $width)
{
parent::__construct("Tiger", $height, $width);
}
public function roar()
{
echo 'ROOARRR';
}
}

Go

// rest of main.go... & fmt importtype Tiger struct {
Animal
}
func (t *Tiger) Roar() {
fmt.Println("Roar");
}

In both cases we’ve added a “roar” method which outputs some text; let’s see the difference in how we create these objects.

PHP

<?phpuse App\Tiger;$tiger = new Tiger(200, 300);
$tiger->roar();
getAnimalInfo($tiger);

In the PHP example, we create a new `Tiger`, and as per our definition, we don’t need to pass the name. But because Tiger is a type of Animal, which implements the AnimalContract we can pass it into getAnimalInfo and it’ll accept it. There’s a slight difference in the way we create the Tiger in Go, but the use case is the same.

Go

// Rest of main.go...var tiger Tigertiger = Tiger{
Animal{
Name: "Tiger",
Height: 200,
Weight: 300,
},
}
tiger.Roar()
GetAnimalInfo(tiger)

As you can see, when we define the Tiger, we define the Animal, which is our embedded struct as if it were a property, but we don’t associate it to one, unlike we do with Name, Height and Weight on the Animal type.

Roundup

After reading this article you should be able to compare and understand the differences in these concepts from PHP to Go, and the similarities they hold.

Want To Learn More?

If you want to find out more about the details of Go’s Structs, Poloymorphism and Struct embedding (Anonymous struct properties) then take a look at these articles:

or checkout some of my other articles talking about the Go language:

https://medium.com/@craigchilds94

Multi-pronged web developer with a passion for cutting edge tooling! https://craigchilds.dev