Php traits implement interface

Интерфейсы для трейтов Перевод

Трейты — это не только копипаст на уровне компилятора, есть свои нюансы, знание и использование которых может вам очень пригодиться.

Исследуем один из таких нюансов и по возможности его усовершенствование на уровне языка. Посмотрим, как мы можем решить одну проблему во время выполнения, но было бы здорово иметь эту возможность в самом PHP.

Проблема:

Трэйты — отличное средство для вынесения общего поведения, когда мы выносим метод в трейт, то можем использовать его в разных классах без дублирования кода. Но эти методы зачастую могут зависеть от других методов (например из других трейтов). И тогда мы не можем быть уверены, что все они будут работать как надо.
Рассмотрим такой пример:

trait GetSalesPrice < /** * @return float */ public function getSalesPrice() < return $this->getWholesalePrice() + $this->getMarkup(); > >

Казалось бы, такой трейт легко использовать, но есть проблема — этот метод использует getMarkup() и getWholeSale(), которых может и не быть в классе-потребителе (класс-потребитель — тот, в котором используется код трейта).

Решение:

К счастью, у php много разных возможностей, позволяющих проверить интерфейсы, классы и трейты на соответсвтие их композиции. Представьте, что мы решили, что трейты должны информировать нас о своих зависимостях там, где мы их будем использовать:

trait GetSalesPrice < /** * @return array */ public function interfacesForGetSalesPrice() < return [ HasWholesalePrice::class, HasMarkup::class ]; >/** * @return float */ public function getSalesPrice() < return $this->getWholesalePrice() + $this->getMarkup(); > >

Можно определить общий трейт, который делает проверку для нужных нам методов. Если интерфейсы не реализованы классом, то кинем исключение:

trait SecureTraits < /** * @param mixed $container * * @return void */ public function secureTraits($container) < $class = new ReflectionClass($container); $traits = $class->getTraitNames(); $interfaces = $class->getInterfaceNames(); foreach ($traits as $trait) < $requiredInterfaces = $this->getInterfacesForTrait( $container, $trait ); foreach ($requiredInterfaces as $interface) < $this->throwForMissingInterfaces( $interfaces, $interface, $trait ); > > > /** * @param mixed $container * @param string $trait * * @return array */ protected function getInterfacesForTrait($container, $trait) < $method = "interfacesFor"; $requiredInterfaces = []; if (method_exists($container, $method)) < $requiredInterfaces = $container->$method(); > return $requiredInterfaces; > /** * @param array $interfaces * @param string $interface * @param string $trait */ protected function throwForMissingInterfaces( $interfaces, $interface, $trait ) < if (!in_array($interface, $interfaces)) < throw new LogicException( "must be implemented for " ); > > >

Это простейший способ использования Reflection (имеется в виду класс Reflection), который когда-либо видел автор 🙂 Метод secureTraits() принимает экземпляр класса и создает для него reflection, откуда мы можем получить список реализованных интерфейсов класса и трейты, которые он использует. Из списка трейтов мы можем узнать, какие методы вызываются , они просто должны быть такими же (быть похожими на) interfacesForTraitName() . Сравниваем их со списком реализованных в классе интерфейсов и при несоответствии кидаем исключение. Можно использовать такой шаблончик:

class ShoePolish implements HasWholesalePrice, HasMarkup < use SecureTraits; use GetSalesPrice; /** * @return ShoePolish */ public function __construct() < $this->secureTraits($this); > /** * @return float */ public function getWholesalePrice() < return 14.99; >/** * @return float */ public function getMarkup() < return $this->getWholesalePrice() * 0.2; > > $shoePolish = new ShoePolish(); print $shoePolish->getSalesPrice();

Источник

Читайте также:  Java iterate arraylist for

PHP Traits for Implementing Interfaces

PHP 5.4 introduced traits. Traits are in many ways like code-assisted copy and paste. They are ways to mix code into a class without inheritance. But what are they good for? When should they be used?

Traits are an excellent way to fulfill the requirements of interfaces.

Interfaces

What is an interface? An interface is a contract that the programmer makes with the program to write code in a certain way. Here’s an example:

Here we created a CRUD interface. What this means is any class that implements this interface promises to have these four functions. Generally, you should also make a personal contract that these functions will have the same format for the parameters and return the same type of value. PHP does not enforce this, but you should. Most IDEs will let you document the functions in the interface, and will copy this documentation to your implementations.

 /** * @param array $options An associate array of options. 
* Always includes $options['query']. * @param array $errors An empty array that will hold
* Error objects. * @return boolean TRUE on success, FALSE on failure. */ public function create(array $options, array &$errors);

Implements

Now we write classes that implement the interface.

class Article implements CRUD < public $table = 'article'; public $db; /** * @param array $options An associate array of options.
* Always includes $options['query']. * @param array $errors An empty array that will hold
* Error objects. * @return boolean TRUE on success, FALSE on failure. */ public function create(array $options, array &$errors) < $columns = []; $values = []; foreach($options['query'] as $key =>$value) < // TODO error checking, validation, etc. $columns[] = "`$key`"; $values[] = "'$value'"; >$sql = "INSERT INTO table> (".implode($key).") VALUES (".implode($value).")"; // DB was set earlier, maybe in the constructor. if ($this->db->query($sql)) < return true; >else < return false; >> . (the rest of the implementations) >

Good so far, but then you write the Comment class, and the User class, and pretty soon you are copying and pasting the exact same implementation code all over the place!

Well, maybe we can make an abstract class:

abstract class mySQLDocument implements CRUD < public $table; public $db; /** * @param array $options An associate array of options. Always includes $options['query']. * @param array $errors An empty array that will hold Error objects. * @return boolean TRUE on success, FALSE on failure. */ public function create(array $options, array &$errors) < $columns = []; $values = []; foreach($options['query'] as $key =>$value) < // TODO error checking, validation, etc. $columns[] = "`$key`"; $values[] = "'$value'"; >$sql = "INSERT INTO table> (".implode($key).") VALUES (".implode($value).")"; // DB was set earlier, maybe in the constructor. if ($this->db->query($sql)) < return true; >else < return false; >> . (the rest of the implementations) > class Article extends mySQLDocument < public $table = 'article'; >class User extends mySQLDocument

That’s looking great! Code is being reused. And everything is going swell. Then you decide, hey, let’s also store articles in Solr, for fast fulltext searching. Now you have to override the methods in Article .

class Article extends mySQLDocument < public $table = 'article'; public function create(array $options, array &$errors) < $success = parent::create($options, &$errors); global $solrClient; $doc = new SolrInputDocument(); foreach($options['query'] as $key =>$value) < $doc->addField($key, $value); > try < $solrClient->addDocument($doc); return true; > catch (SolrClientException $e) < $errors[] = new Error('Solr Error'); return false; >> > 

But what happens when you decide articles and comments should be in Solr? You end up copying and pasting. Maybe you decide to make a class called MysqlWithSolr and extend that. But that is where things start getting messy. In comes out new savior: Traits!

Traits

Traits let you mix code into your classes. Think of it as run-time copy and paste. I’ll start with an example and work from there. In this example, we will just use create.

 trait mySQLCrud < abstract public function getMysqlDB(); abstract public function getMysqlTable(); public function create(array $options, array &$errors) < $columns = []; $values = []; foreach($options['query'] as $key =>$value) < $columns[] = "`$key`"; $values[] = "'$value'"; >$sql = "INSERT INTO ".$this->getMysqlTable() 
." (".implode($key).") VALUES (".implode($value).")"; if ($this->getMysqlDB()->query($sql)) < return true; >else < $errors[] = new Error('MySQL Error'); return false; >> > trait solrCrud < abstract public function getSolrClient(); public function create(array $options, array &$errors) < $client = $this->getSolrClient(); $doc = new SolrInputDocument(); foreach($options['query'] as $key => $value) < $doc->addField($key, $value); > try < $client->addDocument($doc); return true; > catch (SolrClientException $e) < $errors[] = new Error('Solr Error'); return false; >> > class User implements CRUD < use mySQLCrud; public function getMysqlDB()< global $db; // Initialized Elsewhere return $db; >public function getMysqlTable() < return 'user'; >> class Article implements CRUD < use mySQLCrud, solrCrud < mySQLCrud::create as mysqlCreate; solrCrud::create as solrCreate; >public function getSolrClient() < global $solr; // Initialized Elsewhere return $solr; >public function getMysqlDB() < global $db; // Initialized Elsewhere return $db; >public function getMysqlTable() < return 'user'; >public function create(array $options, array &$errors) < $success = $this->mysqlCreate($options, $errors); if ($success) < $success = $this->solrCreate($options, $errors); > return $success; > >

First we set up our interface. We are promising that our CRUD documents will behave the same no matter if the implementation is Solr, MySQL, MongoDB, or carving into stone tablets. The program does not care where the data is store, just that it will be. It will call $success = $article->create($options, $errors) and let the implementation take care of the details.

Next we make a trait for implementing the CRUD interface in MySQL. We declare two abstract functions, one for getting a DB connection, and the other for getting the name of a table. Making these into abstract functions forces a class to implement them when they use the trait. That way, we can guarantee that a DB connection and table name will be available to the create method, which we write out next.

Now we make a trait for implementing the CRUD interface in Solr. Just like above, we have an abstract function to guarantee a connection to Solr.

Next we declare the User class. It implements the CRUD interface, and uses the mySQLCrud trait. This means it must implement the getMysqlDB and getMysqlTable functions. But that’s it. The create method is supplied by the trait.

Finally, we get to the Article class. It too implements CRUD, but it needs both MySQL and Solr. So it simply uses both traits. The issue is that the traits have methods with the same name, so we rename them in the use statement. Now that they are renamed, Article must implement the create method on its own. In our case, it calls the renamed functions, so that it can store the data in MySQL and Solr.

Conclusions

Traits are a great way to implement interfaces. They are especially good for CRUD interfaces where data is stored in different types of database, and some objects may be stored in more than one.

Источник

Оцените статью