📜 ⬆️ ⬇️

We write on php ... static

PHP is a language with dynamic implicit weak typing. Dynamic typing is very useful in many cases, and in general greatly simplifies life, because you concentrate on the values ​​and do not think about what type they are.

Like many php programmers, I thought that static typing is a “complication”. It limits flexibility and in general: how do people work with it? And sincerely did not understand why many experienced programmers prefer languages ​​with static typing and strict type checking.

Typing debate
')
I belonged to the right half of people who know little about types, but at the same time sincerely believe that this is not convenient. And so it was until I became acquainted with one of the strongly typed languages ​​(c #) closely. Since then, my attitude to php and indeed to programming as a whole has changed.

This article will hardly be of interest to the “Titans” of programming, because here you will not find anything revolutionary and new, and the rest interested in improving your code and your own performance are welcome under cat.



To begin with, we will decide what static typing is:

Static typing is determined by the fact that the finite types of variables and functions are set at the compilation stage. Those. the compiler is already 100% sure which type is where.

In php, we do not have a compiler, and all types will be calculated in any case at runtime, but this does not prevent us from strictly following them, adhering to certain rules.

What does all this need in PHP?


Studying c # and simultaneously mastering the product for which tasks were set, without having enough experience and documentation (or rather there was no documentation at all), I was rescued by the fact that, thanks to strong typing, most of the errors were eliminated immediately, and the IDE hints issued based on specified types. I didn’t need documentation to understand what and where to transfer, I couldn’t do something wrong, it just didn’t compile!

This significantly reduced the time, because you immediately see that this method accepts only such parameters, and by creating objects for these parameters, you will know which links are not enough to construct them, and so on.

“Well, it’s really convenient” - you will say - “But you didn’t tell us anything new, PHP has PHPDoc, which most modern IDEs understand and helps to write code on the fly!”

That's right, but for this to work in the same way as in statically typed languages, it is necessary not only to write a comment for each method and class property, but it is important that the whole architecture is designed to work with previously known objects. To build such an architecture, you need to follow certain rules. About them I would like to talk further.

Use objects instead of arrays


Yes exactly. In general, this truth is not new and much has been written about it: objects are easier to expand, methods are easier to add to them, and in general, the path of the PLO.

But I want to consider this question from the other side - from the point of view of building the right architecture for effective programming.

Consider a simple example:
Let's say an event occurs in the system, and we need to pass certain parameters to the handler.
Use for this array:

$eventData = array( 'sender' => $controller, //   'name' => 'onDelete', 'group' => 'global', 'arguments' => array( 'id' => 15 ), ); $eventDispatcher->triggerEvent($eventData); ... /** *    * @param array $eventData */ protected function onDelete($eventData) { //    $eventData   , //       var_dump() //        $model = $eventData['sender']->model->deleteChilds(); } 

The simplest and quite frequent case, nothing complicated, compose an array with the data and send it to the function.

Now imagine that the challenge of this event is somewhere deep in the depths of a program not written by you (or you, but not yesterday).

Submitted? And well, if there is documentation in which the format of this array is described, then you will not have to spend time searching for the calling section of code in order to spy on the structure of the array. Or you use var_dump (), and still spend time to understand what is happening there.

Let's now consider what happens if, instead of an array, we would describe a class and create its object. Change the code:

 class EventData { /** *  ,   * @var BaseController */ public $sender; /** *   * @var string */ public $name; /** *      () * @var string */ public $group; /** *     * @var array */ public $arguments; } $eventData = new EventData(); $eventData->sender = $controller; $eventData->name = 'onDelete'; $eventData->group = 'global'; $eventData->arguments = array('id' => 15); $eventDispatcher->triggerEvent($eventData); ... /** *    * @param EventData $eventData */ protected function onDelete($eventData) { $eventData->sender->model->deleteChilds(); } 

What did we get in the end? Now our favorite IDE tells us what the resulting object contains.



In addition, due to the fact that we have described in detail the type for each attribute in the passed object, we can immediately receive hints on them. Those. typing $ eventData-> controller-> - we can see what is inside the attribute named controller.

Now you do not have to climb somewhere in the jungle of code in order to understand what is transmitted in this object and what type it is. Imagine how grateful you are to other programmers who will work with your code!

If your application is actively using the ActiveRecord pattern, then you are already close to total typing, but if you still get elements from the database as arrays and chase them there, many times from function to function, then you should think about changing architecture.

It was a simple and rather synthetic example. Perhaps in your project in a similar situation, and so the object is used, rather than an array. But look more attentively, there are situations not similar to this one, for example, three-storey arrays are transferred to a function, or the function has a dozen arguments of an incomprehensible type.

Continuing to improve our example, I would not stop at creating one class. Usually in modern web applications many different events take place, and different arguments are passed to them, they can be grouped, and for each group of events you can create your own - a suitable class of arguments.

Do not be afraid to create classes. You do not need to create a separate file for each if they are interconnected with each other.
For example, I would create an EventArguments.php file, and in it I would describe all possible variants of event arguments.

UPD: I do not call to understand my call to replace ALL arrays with objects literally, this is not correct.

Quote from wiki:
An array is an ordered set of data for storing data of the same type, identified by one or more indices.

It is in this vein that arrays should be used. those. store some ordered data of the same type.

Recklessly changing arrays on objects can do more harm than good. Therefore, you have to determine for yourself (and for your project) the line between usability of objects and system performance.

Initialize variables with the correct type.


Just for example:

  /** *   * @return array */ public function getChildren(){ if(!$this->isLeaf) { return $this->find('where id = :id', array(':id', $this->id)); } } ... foreach($model->getChildren() as $child) { //     } 

Familiar? The problem lies in the fact that if the condition! $ This-> isLeaf fails, then the result of the function will not be an array at all, and we will have to write a bunch of sample code so that foreach does not fall due to incorrect arguments.

It should be:

  /** *   * @return array */ public function getChildren(){ //     ,     -   . //         ,     ,   . $children = array(); if(!$this->isLeaf) { $children = $this->find('where id = :id', array(':id', $this->id)); } return $children; } 

Now if the condition! $ This-> isLeaf fails, then the result of the function will be an empty array that we defined at the beginning, and the foreach to which the value falls from this function will not fall if something goes wrong.

The considered example is only the tip of the iceberg. If you always immediately define a variable with the necessary, previously known type, you can save a lot of time. And also to write more beautiful code without additional checks worsening the code perception.

By the way, this concerns not only php. For example, in JavaScript in the V8 engine, determining the type of a variable at the stage of its creation speeds up the execution of the script, as it does not force the engine to do unnecessary transformations (more in this article )

Strong typing tools.


PHP has weak typing, which means that it does not check types during operations with them. Those. you can add a string with a number, or check the equality between the scalar object, and the interpreter will not tell you anything. To force PHP to behave strictly typed we cannot. However, in its bundle, PHP has several handy (albeit not always logical) type control tools.

For example, when defining a function, you can explicitly indicate what type its parameters should be:

  public function triggerEvent(EventData $event) { //          EventData    } ... $dispatcher->triggerEvent(new EventData); //  $dispatcher->triggerEvent(new EventDataChild); //EventDataChild -  EventData,   $dispatcher->triggerEvent(new AnotherClass()); //Catchable fatal error: Argument 1 passed to EventDispatcher::triggerEvent() must be an instance of EventData, instance of new AnotherClass given 

Thus, we, without additional checks, have protected ourselves from accidental transfer to the function of the parameter of the wrong type. PHP itself will check the type of the parameter passed and will give an error if it does not match.

Quote from the manual of php.net/oop5.typehinting type control :

At the moment, functions have the ability to force parameters to be either objects (by specifying the class name in the function prototype), or interfaces, or arrays (starting with PHP 5.1), or a callable callback (starting with PHP 5.4).

However, if NULL was used as the default parameter value, this will also be valid as an argument for the subsequent call.

If a class or interface is specified for type control, then all its descendants or implementations are also valid.
Type control cannot be used with scalar types such as int or string. Traits are also unacceptable.

Making a conclusion from the above, we can specify almost everything except scalar types, but PHP has a solution to this account. The standard SPL library contains several interesting classes designed to solve this problem:

Since these types are classes (oil is oil, but in the context of php where int is a type, but not a class, it is permissible) they can be specified in the function definition, thereby preventing the transfer to the function, for example, strings where boolean is required. Accordingly, you will have to use these classes throughout the code, instead of standard scalars.

Honestly, I did not use it, because in my opinion this is overkill. In addition, these classes are not included in the standard PHP distribution, and are distributed by the PECL package, which makes them difficult to use.

Use PHPDoc wherever possible.


PHPDoc and its support in the IDE is a great PHP time saving tool for the programmer.

Since the syntax of the language and its ideology do not allow you to set the type of a variable when creating it, it is very difficult for your IDE to understand what a variable is. So help her - specify the type in the comments, and your IDE will thank you with tips during the work.

This is another step towards full typing your application. If you know what gets and what returns the desired method, you will not have difficulty using it.

I adhere to the following rules:
All public class attributes must have a comment describing the type;
All public methods must also have a description of what they do and a description of each parameter, including what they return.

Small hints for those unfamiliar with PHPDoc (this is all correct for the NetBeans environment)

  //     , //         @return //  IDE             /** *      EventData * @return EventData */ public function getEventData(){ return new EventData(); } //   ,    ,     : //@return EventHandler[] //  -      . //   ,    ,      // //: /**    EventHandler * @return EventHandler[] */ public function getEventHandlers(){ return $this->handlers; } public function triggerEvent(){ foreach ($this->getEventHandlers() as $handler) { $handler; //   IDE  ,  $handler    EventHandler,     } } public function getModel(){ //      //(    .  ,      ) //   ,         .    : $model = NodeFactory::byId($id); /* @var $model Folder */ $model; //  IDE         Folder } 


Conclusion


What would anyone say, but PHP has grown to a fairly powerful tool. Dynamic typing is what allows it to be as flexible and simple as possible. But along with it come complexity.



Static typing allows to improve productivity and reduce the number of errors in the code.

From comments on Habré to article Likbez on typification :
In tasks whose complexity makes you feel a wild delight at the possibility of automatic checking at least something, languages ​​with strong static typing overtake everything else. The complexity of the tasks solved during site building is gradually approaching this level.

We can not change the relationship of PHP to types, and it is not necessary. We also cannot take overnight and translate all our projects into any other language, for this there is neither time nor opportunity.
But thanks to a well-designed architecture, the use of type control in the prototypes of your functions, as well as documentation with PHPDoc, you can unload yourself from unnecessary work and search for errors in your projects.

By adhering to the advice outlined in this article, we will be able to combine the best of both worlds, and get a powerful and flexible tool for developing complex projects.

Useful links:


Likbez on typing in programming languages
Type Control in PHP
PHP SPL Types
Static code analysis (Wikipedia)

Source: https://habr.com/ru/post/162535/


All Articles