PHP-OOP extending two classes?

2019-06-22 14:06发布

问题:

I am very beginner to OOP and now I am trying to write some PHP class to connect with FTP server.

class ftpConnect {
  private $server;
  private $user;
  private $password;

  private $connection_id;
  private $connection_correct = false;

  public function __construct($server, $user = "anonymous", $password = "anonymous@mail.com") {

    $this->server   = $server;
    $this->user     = $user;
    $this->password = $password;

    $this->connection_id      = ftp_connect($this->server);
    $this->connection_correct = ftp_login($this->connection_id, $this->user, $this->password);

    if ( (!$this->connection_id) || (!$this->connection_correct) ){
        echo "Error! Couldn't connect to $this->server";
        var_dump($this->connection_id);
        var_dump($this->connection_correct);
        return false;
    } else {
        echo "Successfully connected to $this->server, user: $this->user";
        $this->connection_correct = true;
        return true;
    }
  }
}

I reckon that body of the class is insignificant at the moment.

Main issue is that I have some problems with understanding OOP idea.

I wanted to add sending emails every time, when the code is run. I have downloaded PHPMailer Class and extended my class with it:

class ftpConnect extends PHPMailer {...}

I have added some variables and methods and everything works as expected to that point.

I thought: why not to add storing everything in database. Everytime user runs above code, proper information should be stored in database.

I could edit my ftpConnect class and add database connecting to the constructor, and some other methods to updating tables. But database connecting and all that stuff could be used by other classes in the future, so it definitely should be implemented in seperate class. But my "main" ftpConnect class already extends one class and could not extend not a single one more.

I have no idea how can I resolve this problem. Maybe my ftpConnect class is to complex and I should somehow divide it into couple smaller classes? Any help is much appreciated.

回答1:

For starters I think you have a design flaw in your class. Your constructor is doing work. That's not what a constructor should do in proper OOP. Your constructor should just set the properties and you should have a separate method connect().

Second ftpConnect should never ever extend PHPMailer. They are two totally different things. Read about the Liskov substitution principle it is part of the SOLID principles.

If your class needs to do something with a database or needs to send mails you need to inject those instances into your class instead of extending them. This is called dependency injection and this will make it easy to do unit tests later, because you can easily use a mock mailer class or a mock database class.

If you want to send mails, have database access and use FTP you would need at least 3 different (separated) classes (probably even more to do some mapping for the db etc). Basically every class should have one responsibility and one only. This is called the single responsibility principle.

For some general references see:

  • Guide: Writing Testable Code
  • Google Clean Code Talks


回答2:

It's probably a question of composition over inheritance See this Prefer composition over inheritance? Just use the mailer object inside your class and same goes for DB rather than your class extending any of them.

class my_class
{
    private $mailer;

    public function __constructor()
    {
         $this->mailer = new Mailer();
    }
}


回答3:

For the database storing part, you could create a seperate class, which has a connection to your database. You can pass an instance of this class to your ftpConnect class via it's constructor. Give your fptConnect class a property where you can store this new database object in, this way you can access this object in your entire ftpConnect class.

Your ftpConnect class currently has a constructor which will either return true or false, don't let the constructor return any value, because it wil need to return an instance of the ftpConnect class which you will need to store inside a variable. You could therefore make the actual connection in a seperate method. So your class could look something like this.

class FtpConnect {
    private $server;
    private $user               = "anonymous";
    private $password           = "anonymous@mail.com";
    private $connection_id;
    private $connection_correct = false;

    //this will take care of the storage
    private $database_handler;

    public function __construct($server, $user, $password, $database_handler) {
        $this->server   = $server;
        $this->user     = $user;
        $this->password = $password;
        $this->database_handler = $database_handler;

        //store the appropriate data, this will be done everything a new instance is created
        $this->database_handler->store_data($data_to_store);
    }

    public function connect() {

        //data to store, everytime a connection is made
        $this->database_handler->store_data($data_to_store);

        $this->connection_id      = ftp_connect($this->server);
        $this->connection_correct = ftp_login($this->connection_id, $this->user, $this-      >password);

        if ( (!$this->connection_id) || (!$this->connection_correct) ){
            echo "Error! Couldn't connect to $this->server";
            var_dump($this->connection_id);
            var_dump($this->connection_correct);
            return false;
        } else {
            echo "Successfully connected to $this->server, user: $this->user";
            $this->connection_correct = true;
            return true;
       }
    }
}

Ofcourse this is a possible way to do it, there wil probably be much more elegant solutions. The way mailing is done could be realised using the same concept.

From outside it will look something like this

$ftp = new FptConnect('server', 'user', 'password', new DbHandler('server', 'user', 'password', 'host'));

$ftp->connect();


标签: php oop