SOLID in practice. Open-closed principle and ActiveQuery Yii2

Once in a working conversation, one of my fellow programmers noticed that all sorts of principles and patterns of software design are good to use when doing test tasks, but in real, combat projects, they are usually not applicable. Why is that? There are two main reasons: 





  • Principles and patterns take too long to implement. 





  • The code becomes cumbersome and difficult to understand. 





In a series of articles "In Practice" I will try to dispel these preconceptions by demonstrating cases that implement design principles in practical tasks in such a way that this code is not too complicated and takes a reasonable amount of time to write. Here is the first article in this series. 





The starting point

We are working on a project in Yii2 , in which we use  ActiveRecord



. The client code loads a set of data using the  ActiveRecord::find()







class ClientClass  


    //  ... 

    public function buildQuery(): ActiveQueryImplementation 
    
        $query = ActiveRecordModel::find(); //   ActiveQuery   
        $query->active()->unfinished(); //  ,     ActiveQuery         

        return $query; //    ActiveQuery      ,  $query->all(); 
    } 

    //  ... 

      
      



This code applies a ActiveQueryInterface



fixed set of conditions to the instance that implements   and returns the configured instance for later use. 





What if you need to add new conditions to the request?

, , . 





$query->active()->unfinished()->newConditionA()->newConditionB(); 
      
      



! , , . 





, ? ? 





. , , , , . ? ... 





-

 , - SOLID : 





. 





   ,   .  





-,    , , .









class ClientClass  
{ 
    /** 
     * @var ActiveQueryFilter[] 
     */ 
    public $filters = []; //        

    //  ... 

    public function buildQuery(): ActiveQueryImplementation
    
        $query = ActiveRecordModel::find(); 
        $query->active()->unfinished();   
        $this->applyFilters($query); //     

				return $query; 
    } 

    private function applyFilters(ActiveQueryImplementation &$query): void  
    { 
        foreach ($this->filters as $filter) { 
            $filter->applyTo($query); 
        } 
    } 

    //  ... 

      
      



 ActiveQueryFitler



  applyTo()



, . 





interface ActiveQueryFilter 
{ 
    public function applyTo(ActiveQuery $query): void
      
      



 ActiveQueryFilter







class NewConditionsAAndBFilter implements ActiveQueryFilter 
{ 
    public function applyTo(ActiveQuery $query): void  
    
        $query->newCondtionA()->newConditionB(); 
    } 
      
      



 

  • We have solved our problem by  completing the  original method with just one call (minimal intervention in the original code). 





  • The default behavior of the original method has not changed. 





  • The new method called from the original method  applyFilters()



    does not implement its own logic - all logic is delegated to the filter classes. Thus, we have left the behavior of the entire original class unchanged. 












All Articles