DEV Community

david duymelinck
david duymelinck

Posted on • Edited on

Php Fun: Rustyfy classes

I read a Rust dependency injection post and I thought what would it be like to keep PHP code as close to the Rust code as possible.

I'm shamelessly stealing the example code.

The contract and an implementation

pub trait Logger {
    fn log(&self, message: &str);
}

use std::io::{self, Write};

pub struct ConsoleLogger;

impl Logger for ConsoleLogger {
    fn log(&self, message: &str) {
        writeln!(io::stdout(), "{}", message).expect("Failed to write to console");
    }
}
Enter fullscreen mode Exit fullscreen mode

A Rust trait is not a PHP trait. In Rust a trait is what an interface is in PHP. A PHP trait is used to make methods and properties available in multiple classes.

interface LoggerTrait 
{
   public function log(str $message);
}

class ConsoleLoggerStruct
{}

class ConsoleLoggerImpl extends ConsoleLoggerStruct implements LoggerTrait
{
   public function log(str $message)
   {
      echo $message;
   }
}
Enter fullscreen mode Exit fullscreen mode

I used suffixes to make the Rust keywords more visible in PHP.

The strange thing for PHP is the empty class, ConsoleLoggerStruct.
In Rust this called a unit-like struct. A unit in Rust is the default return when there is no return, in PHP this is null.

In Rust the struct and the implementation have the same name, but in PHP this is not possible in the same namespace. So if you want an experience closer to the Rust naming in PHP you can use two directories to separate the class types.

Dependency injection

pub struct Application<L: Logger> {
    logger: L,
}

impl<L: Logger> Application<L> {
    pub fn new(logger: L) -> Self {
        Self { logger }
    }

    pub fn run(&self) {
        self.logger.log("Application is running!");
    }
}

fn main() {
    let logger = ConsoleLogger;
    let app = Application::new(logger);
    app.run();
}
Enter fullscreen mode Exit fullscreen mode

We see a few things coming back in this code.

class ApplicationStruct
{
   public function __construct(private LoggerTrait $logger)
   {}
}

class ApplicationImpl extends ApplicationStruct
{
   public function run()
   {
      $this->logger->log('The application is running.');
   }
}

$logger = new ConsoleLoggerImpl();
$app = new ApplicationImpl($logger);
$app->run();
Enter fullscreen mode Exit fullscreen mode

Here we see a Rust struct is a object with properties in PHP. I wouldn't call it a DTO because its purpose is to transfer data, the struct can also contain instances.

For the rest there is nothing out of the ordinary in the PHP code. It is a basic way to do dependency injection.

Conclusion

Rust prefers composition over inheritance, there is no inheritance as there is in PHP. If you want inheritance you can do that with traits.

trait Animal {
    fn name(&self) -> &'static str;
    fn noise(&self) -> &'static str;
}

trait Speaks: Animal {
    fn speak(&self) {
        println!("The {} says {}", self.name(), self.noise());
    }
}
Enter fullscreen mode Exit fullscreen mode

While composition is a good thing in PHP as well, having inheritance improves the developer experience by implementing methods in a parent class.

Is separating the properties from the methods a good pattern in PHP? At the moment I don't see a benefit.
Having the same name feels like a unnecessary constriction. On the other hand it makes naming a bit more consistent.
I will stick with DTO's for data arguments in classes, and do constructor and method dependency injection as before.
I'm fine with the composition and inheritance mix that PHP makes possible.

Top comments (0)