Um dos padrões de projeto mais legais é o State, com ele, um objeto pode mudar seu comportamento em tempo de execução dependendo do estado corrente e cada implementação de estado preocupa-se apenas com o que deve fazer, deixando assim um código limpo e totalmente reutilizável.
Cada ConcreteState implementa um comportamento específico permitindo, dessa forma, que novos comportamentos sejam anexados.
No caso de uma conexão SSH:
Nosso contexto inicia-se como SSHStateClosed, assim que executamos o método open() nosso estado passa a ser SSHStateEstabilished e podemos verificar a assinatura do host e autenticar; Assim que autenticado o estado passa à SSHStateListen para que possamos enviar comandos ao servidor remoto.
Como uma autenticação pode ser de várias formas, deixamos a implementação separada em um objeto específico, dessa forma, podemos autenticar com usuário e senha ou, se for o caso, utilizando certificados digitais (entre outras).
Um comando deveria ser implementado da mesma forma, porém, para simplificar as coisas, vamos trabalhar com strings mesmo:
com/ssh/auth/SSHAuthenticatePassword.php
<?php /** * @author João Batista Neto * @package ssh * @category ssh, state, design pattern */ namespace com\ssh\auth; /** * @see ISSHAuthentication */ use com\ssh\interfaces\ISSHAuthentication; /** * Implementa uma autenticação utilizando nome de usuário e senha * @author João Batista Neto * @final * @package ssh * @category ssh, state, authentication, design pattern */ final class SSHAuthenticatePassword implements ISSHAuthentication { /** * @var string */ private $user; /** * @var string */ private $pswd; /** * Constroi o objeto de autenticação * @param string $user * @param string $pswd */ public function __construct( $user , $pswd ){ $this->user =& $user; $this->pswd =& $pswd; } /** * Efetua a autenticação * @param resource $resource * @throws InvalidArgumentException se o recurso não for válido */ public function authenticate( &$resource ){ if ( is_resource( $resource ) ){ return \ssh2_auth_password( $resource , $this->user , $this->pswd ); } else { throw new \InvalidArgumentException( 'Recurso inválido.' ); } return false; } /** * Recupera o nome do usuário * @return string */ public function getUser(){ return $this->user; } }
com/ssh/interfaces/AbstractSSHState.php
<?php /** * @author João Batista Neto * @package ssh * @subpackage interfaces * @category ssh, state, design pattern */ namespace com\ssh\interfaces; /** * Interface para um estado de conexão * @abstract * @author João Batista Neto * @package ssh * @subpackage interfaces * @category sh, state, design pattern */ abstract class AbstractSSHState implements ISSHState { /** * @var resource */ protected $resource; /** * Autentica o usuário * @param ISSHAuthentication $auth * @param ISSHConnection $context * @return boolean * @throws BadMethodCallException se o estado não implementar o método authenticate */ public function authenticate( ISSHAuthentication $auth , ISSHConnection $context ){ throw new \BadMethodCallException( sprintf( '%s não implementa o método %s' , get_class( $this ) , __METHOD__ ) ); } /** * Executa um comando no servidor remoto * @param string $command * @param ISSHConnection $context * @return boolean * @throws BadMethodCallException se o estado não implementar o método execute */ public function execute( $command , ISSHConnection $context ){ throw new \BadMethodCallException( sprintf( '%s não implementa o método %s' , get_class( $this ) , __METHOD__ ) ); } /** * Recupera a fingerprint do servidor * @param ISSHConnection $context * @return string * @throws BadMethodCallException se o estado não implementar o método execute */ public function getFingerprint( ISSHConnection $context ){ throw new \BadMethodCallException( sprintf( '%s não implementa o método %s' , get_class( $this ) , __METHOD__ ) ); } /** * Abre uma nova conexão * @param string $host * @param integer $port * @param ISSHConnection $context * @return boolean * @throws BadMethodCallException se o estado não implementar o método open */ public function open( $host , $port = 22 , ISSHConnection $context ){ throw new \BadMethodCallException( sprintf( '%s não implementa o método %s' , get_class( $this ) , __METHOD__ ) ); } /** * Define o recurso da conexão * @param resource $resource * @throws InvalidArgumentException se o recurso não for válido */ public function setResource( &$resource ){ if ( !is_resource( $resource ) ){ throw new \InvalidArgumentException( 'Recurso inválido.' ); } else { $this->resource =& $resource; } } }
com/ssh/interfaces/ISSHAuthentication.php
<?php /** * @author João Batista Neto * @package ssh * @subpackage interfaces * @category ssh, state, design pattern */ namespace com\ssh\interfaces; /** * Interface para autenticação SSH * @author João Batista Neto * @package ssh * @subpackage interfaces * @category ssh, state, design pattern */ interface ISSHAuthentication { /** * Efetua a autenticação do usuário * @param resource $resource */ public function authenticate( &$resource ); /** * Recupera o nome do usuário * @return string */ public function getUser(); }
com/ssh/interfaces/ISSHConnection.php
<?php /** * @author João Batista Neto * @package ssh * @subpackage interfaces * @category ssh, state, design pattern */ namespace com\ssh\interfaces; /** * Interface para uma conexão SSH * @author João Batista Neto * @package ssh * @subpackage interfaces * @category ssh, state, design pattern */ interface ISSHConnection { /** * Autentica um usuário * @param ISSHAuthentication $auth * @return boolean */ public function authenticate( ISSHAuthentication $auth ); /** * Executa um comando no servidor remoto * @param string $command * @return boolean */ public function execute( $command ); /** * Recupera a fingerprint do servidor * @return string */ public function getFingerprint(); /** * Abre uma nova conexão * @param stirng $host * @param integer $port * @return boolean */ public function open( $host , $port ); /** * Modifica o estado da conexão * @param ISSHState $state */ public function changeState( ISSHState $state ); }
com/ssh/interfaces/ISSHState.php
<?php /** * @author João Batista Neto * @package ssh * @subpackage interfaces * @category ssh, state, design pattern */ namespace com\ssh\interfaces; /** * Interface para um estado de conexão * @author João Batista Neto * @package ssh * @subpackage interfaces * @category ssh, state, design pattern */ interface ISSHState { /** * Autentica o usuário * @param ISSHAuthentication $auth * @param ISSHConnection $context */ public function authenticate( ISSHAuthentication $auth , ISSHConnection $context ); /** * Executa um comando no servidor remoto * @param string $command * @param ISSHConnection $context */ public function execute( $command , ISSHConnection $context ); /** * Recupera a fingerprint do servidor * @param ISSHConnection $context * @return string */ public function getFingerprint( ISSHConnection $context ); /** * Abre uma nova conexão * @param string $host * @param integer $port * @param ISSHConnection $context */ public function open( $host , $port = 22 , ISSHConnection $context ); /** * Define o recurso da conexão * @param resource $resource */ public function setResource( &$resource ); }
com/ssh/state/SSHStateClosed.php
<?php /** * @author João Batista Neto * @package ssh * @category ssh, state, design pattern */ namespace com\ssh\state; /** * @see AbstractSSHState */ use com\ssh\interfaces\AbstractSSHState; /** * @see ISSHConnection */ use com\ssh\interfaces\ISSHConnection; /** * Implementa o estado para uma conexão fechada * @author João Batista Neto * @final * @package ssh * @category ssh, state, design pattern */ final class SSHStateClosed extends AbstractSSHState { /** * @staticvar * @var ISSHConnection */ static private $context; /** * Abre uma nova conexão * @param string $host * @param integer $port * @param ISSHConnection $context * @return boolean * @throws RuntimeException se não for possível estabelecer a conexão * @uses SSHStateEstabilished */ public function open( $host , $port = 22 , ISSHConnection $context ){ $resource = \ssh2_connect( $host , $port , array( 'disconnect' => array( 'SSHStateClosed' , 'disconnect' ) ) ); if ( !is_resource( $resource ) ){ throw new \RuntimeException( 'Não foi possível estabelecer a conexão.' ); } else { $estabilished = new SSHStateEstabilished(); $estabilished->setResource( $resource ); self::$context =& $context; self::$context->changeState( $estabilished ); } return true; } /** * Muda o estado da conexão para SSHStateClosed caso o servidor envie um disconnect * @static */ static public function disconnect(){ self::$context->changeState( new SSHStateClosed() ); } }
com/ssh/state/SSHStateEstabilished.php
<?php /** * @author João Batista Neto * @package ssh * @category ssh, state, design pattern */ namespace com\ssh\state; /** * @see AbstractSSHState */ use com\ssh\interfaces\AbstractSSHState; /** * @see ISSHConnection */ use com\ssh\interfaces\ISSHConnection; /** * @see ISSHAuthentication */ use com\ssh\interfaces\ISSHAuthentication; /** * Implementa o estado para uma conexão estabelecida * @author João Batista Neto * @final * @package ssh * @category ssh, state, design pattern */ final class SSHStateEstabilished extends AbstractSSHState { /** * Efetua a autenticação do usuário * @param ISSHAuthentication $auth * @param ISSHConnection $context * @return boolean * @throws RuntimeException se não for possível autenticar o usuário * @uses SSHStateAuthenticated */ public function authenticate( ISSHAuthentication $auth , ISSHConnection $context ){ if ( !$auth->authenticate( $this->resource ) ){ throw new RuntimeException( sprintf( 'Não foi possível autenticar o usuário %s.' , $auth->getUser() ) ); } else { $authenticated = new SSHStateListen(); $authenticated->setResource( $this->resource ); $context->changeState( $authenticated ); } return true; } /** * Recupera o hash da chave do servidor da conexão ativa * @return string * @throws LogicException se o estado não implementar o método execute */ public function getFingerprint( ISSHConnection $context ){ return \ssh2_fingerprint( $this->resource , \SSH2_FINGERPRINT_MD5 | \SSH2_FINGERPRINT_HEX ); } }
com/ssh/state/SSHStateListen.php
<?php /** * @author João Batista Neto * @package ssh * @category ssh, state, design pattern */ namespace com\ssh\state; /** * @see AbstractSSHState */ use com\ssh\interfaces\AbstractSSHState; /** * @see ISSHConnection */ use com\ssh\interfaces\ISSHConnection; /** * Implementa o estado para uma conexão autenticada * @author João Batista Neto * @final * @package ssh * @category ssh, state, design pattern */ final class SSHStateListen extends AbstractSSHState { /** * @var resource */ private $shell; /** * Executa um comando no servidor remoto * @param string $command * @param ISSHConnection $context * @return string A saída do servidor remoto * @throws RuntimeException se não for possível executar o comando */ public function execute( $command , ISSHConnection $context ){ $ret = null; $stream = \ssh2_exec( $this->resource , $command ); if ( is_resource( $stream ) ){ \stream_set_blocking( $stream , true ); while ( ( $line = \fgets( $stream , 4096 ) ) !== false ){ $ret .= $line; } } else { throw new \RuntimeException( 'Não foi possível executar o comando.' ); } return $ret; } }
com/ssh/SSHConnection.php
<?php /** * @author João Batista Neto * @package ssh * @category ssh, state, design pattern */ namespace com\ssh; /** * @see ISSHConnection */ use com\ssh\interfaces\ISSHConnection; /** * @see ISSHAuthentication */ use com\ssh\interfaces\ISSHAuthentication; /** * @see ISSHState */ use com\ssh\interfaces\ISSHState; /** * @see SSHStateClosed */ use com\ssh\state\SSHStateClosed; /** * Implementa uma conexão com um servidor remoto via SSH * @author João Batista Neto * @final * @package ssh * @category ssh, state, design pattern */ class SSHConnection implements ISSHConnection { /** * @var ISSHState */ private $state; /** * Cria o objeto de conexão */ public function __construct(){ $this->state = new SSHStateClosed(); } /** * Faz a autenticação no servidor * @param ISSHAuthentication $auth * @return boolean */ public function authenticate( ISSHAuthentication $auth ){ return $this->state->authenticate( $auth , $this ); } /** * Executa um comando no servidor * @param string $command * @return boolean */ public function execute( $command ){ return $this->state->execute( $command , $this ); } /** * Recupera a fingerprint do servidor * @return string */ public function getFingerprint(){ return $this->state->getFingerprint( $this ); } /** * Abre uma conexão com um servidor remoto * @param string $host * @param integer $port * @return boolean */ public function open( $host , $port = 22 ){ return $this->state->open( $host , $port , $this ); } /** * Modifica o estado da conexão * @param ISSHState $state */ public function changeState( ISSHState $state ){ $this->state =& $state; } }
Agora, para conectar em um servidor remoto via PHP é simples:
<?php use com\ssh\SSHConnection; use com\ssh\auth\SSHAuthenticatePassword; $teste = new SSHConnection(); $teste->open( '127.0.0.1' ); $teste->authenticate( new SSHAuthenticatePassword( 'usuario' , 'senha' ) );
echo $teste->execute( 'ls -l /var/www/html/xxx/com/ssh' );
No meu caso, a saída foi:
total 16 drwxr-xr-x. 3 neto apache 4096 Mar 11 18:18 auth drwxr-xr-x. 3 neto apache 4096 Mar 11 18:18 interfaces -rw-r--r--. 1 neto apache 1681 Mar 11 18:17 SSHConnection.php drwxr-xr-x. 3 neto apache 4096 Mar 12 06:35 state