⬆️ ⬇️

PHP vs. Ruby: Let's live together





It often happens that a developer who is fluent in one language and has tried a new one for himself, makes hasty conclusions and comparisons. Typically, these publications are quite useless, but catchy headlines give good traffic.



I decided that it would be much more interesting to make a more honest comparison from the point of view of a developer who likes both languages ​​and who has a decent experience working with them. For example, with PHP and Ruby. And the task here is not to find out which one is “better”. I just want to emphasize the properties that I like about Ruby and its ecosystem.



Conceptual differences



Different languages ​​are often compared with each other to choose the best one, although the differences between them are more of an ideological nature. The same language property can be a boon for some developers, and a nightmare for others. And before we get to the point, you need to explain some of the fundamental differences between languages.

')

Method, variable, property?



PHP uses a different syntax for accessing properties, methods, and variables, unlike Ruby.



Php

$this->turtle #   $this->bike() #  $apple #  Ruby @turtle #   turtle # " "   attr_reader: :turtle bike #  apple #  


Pedants will notice that attr_reader :turtle dynamically determines the method used as a getter for @turtle , so turtle and bike are one and the same. In this case, the PHP developer does not understand where it comes from, looking at using turtle without explicitly defining the name of a method or variable.



This can sometimes cause confusion. For example, your teammate will replace the attr_reader elements with full methods, or vice versa, which will cause misunderstandings. It must be said that with the use of some very flexible APIs, this is a perfectly acceptable move, with the help of which you can implement quite bold schemes.



You have deleted the field, but the JSON contract considers it to be still present, but there is a lot of code left?



 class Trip def canceled_at nil end end 


This works fine: if something calls trip.canceled_at , it will get nil instead of a field. And later it can be correctly removed.



Type indications (type hint) against duck typing



In the world of PHP, type indications are a strange and wonderful thing at the same time. In languages ​​like Go, you must specify both argument types and return value types. An optional type specification for arguments appeared in PHP from version 5. You can also query arrays, specific names for classes, interfaces, or abstract objects, as well as recently called objects.



In PHP 7.0, an indication of types for return values, as well as hinting support for int , string , float , etc., appeared. In addition, scalar type indications were introduced. This functionality has caused many disputes and debates, but as a result has been implemented. Its use depends entirely on the preferences of the programmer himself, which is good news in the light of the diversity of PHP users.



Ruby has none of this.



Duck typing is an excellent approach supported by part of the PHP community. Instead of stating: “The argument must be an instance of a class that implements FooInterface ”, with the result that FooInterface will have a method bar(int $a, array $b) , you can say otherwise: “The argument can be anything, just to react to bar method And if it doesn’t react, we’ll think of something else. ”



Ruby

 def read_data(source) return source.read if source.respond_to?(:read) return File.read(source.to_str) if source.respond_to?(:to_str) raise ArgumentError end filename = "foo.txt" read_data(filename) #=>   foo.txt    File.read() input = File.open("foo.txt") read_data(input) #=>   foo.txt    #    


This is a really flexible approach, although some people don’t like this code. Especially in PHP, in which int(0) and int(1) in weak mode are considered valid Boolean values ​​that take any value, so it’s quite risky to hope that everything will work as it should. In PHP, we could just define two different methods / functions:



 function read_from_filename(string $filename) { $file = new SplFileObject($filename, "r"); return read_from_object($file); } function read_from_object(SplFileObject $file) { return $file->fread($file->getSize()); } $filename = "foo.txt"; read_from_filename($filename); $file = new SplFileObject($filename, "r"); read_from_object($file); 


In other words, you can easily use duck typing in PHP:



 function read_data($source) { if (method_exists($source, 'read')) { return $source->read(); } elseif (is_string($source)) { $file = new SplFileObject($source, "r")); return $file->fread($file->getSize()); } throw new InvalidArgumentException; } $filename = "foo.txt"; read_data($filename); #=>   foo.txt    # SplFileObject->read(); $input = new SplFileObject("foo.txt", "r"); read_data($input); #=>   foo.txt    #    


It is a very popular misconception that Ruby is “better” than PHP due to the possibility of using “duck” typing. PHP allows you to use both approaches, choose any. And this PHP favorably differs from Ruby, in which there is no possibility to use type hint, even if you really want to. Many PHP programmers do not like type designations and would like them to not exist at all. Unfortunately for them, however, in PHP 7.0 there was even more type hint.



By the way, earlier in Python, too, there were no type indications, but recently they were nevertheless implemented .



Fun features



Having dealt with the above, you can focus on funny things. Some of them you can use often enough, if not regularly.



Nested classes



For a PHP developer, this is a rather exotic thing. Our classes live in the namespace, and both the class and the space itself can have the same name. So if we have a class that is related only to one particular class, then we simply add it to the namespace. Take the Box class, which can throw an ExplodingBoxException exception:



 namespace Acme\Foo; class Box { public function somethingBad() { throw new Box\ExplodingBoxException; } } 


This exception must exist somewhere. You can put it on top of a class, but we have two classes in one file ... well, in general, it will amuse many. PSR-1 will be violated, which states:



“This means that each class is in a separate file, and in the namespace it occupies at least one level: the name of the top-level supplier”


Here is its separate file:

 namespace Acme\Foo\Box; class ExplodingBoxException {} 


To download this exception, you will have to use the autoloader and access the file system again. But resources are spent on it! In PHP 5.6, the redundancy of repeated requests is reduced when you turn on the opcode cache, but you still get too much work.



In Ruby, you can nest one class in another:

 module Acme module Foo class Box class ExplodingBoxError < StandardError; end def something_bad! raise ExplodingBoxError end end end end 


This can be done both when defining a class and outside it:



 begin box = Acme::Foo::Box.new box.something_bad! rescue Acme::Foo::Box::ExplodingBoxError # ... end 


It seems strange, but very comfortable. Does the class have only one class? Group them!



Another example is database migrations. They are used in many popular PHP frameworks, from CodeIgniter to Laravel. If you refer to a model or another class in the migration, and then change it, then the old migrations very strangely break.



In Ruby, this problem is beautifully solved using nested classes:



 class PopulateEmployerWithUserAccountName < ActiveRecord::Migration class User < ActiveRecord::Base belongs_to :account end class Account < ActiveRecord::Base has_many :users end def up Account.find_each do |account| account.users.update_all(employer: account.name) end end def down #  ,  ID ,     # « » User.where.not(account_id: nil).update_all(employer: nil) end end 


Instead of the global declared classes, the nested version of the User and Account ORM models will be used. That is, if necessary, they can perform for us the role of snapshots. This is much more useful than calling the code in an environment where the rules of the game can change at any time. For some, all this sounds crazy, but only until you come across migration problems.



Debugger



XDebug is a great thing. The use of breakpoints has made a small revolution in debugging PHP applications, enabling the implementation of a more advanced “ var_dump() + update” scheme that is widespread among novice developers.



In other words, it may cost you a lot of effort to get XDebug to work with your IDE, find the right addon (if necessary), configure php.ini so that you can use zend_extension=xdebug.so with your CLI and web version of the application, get sent checkpoints , even if you use Vagrant, etc.



Ruby has a slightly different approach. As with the debugging of JavaScript in the browser, you can simply type the word debugger into the code, thereby obtaining a checkpoint. When this line is executed, it does not matter that it is a $ rails server , a unit test, an integration test, etc. - you will have access to a REPL instance that can work with your code.



There are several other debuggers, the most popular of which are pry

pry

and byebug . Both of them are gems, and to install them you need to add the code to the Gemfile via the Bundler:



 group :development, :test do gem "byebug" end 


This is analogous to the dev Composer dependency. If you are using Rails, then after installation, just call the debugger . Otherwise, you must first execute require "byebug" .



The Rails guide describes how everything works after introducing the appropriate keyword into your application:



 [1, 10] in /PathTo/project/app/controllers/articles_controller.rb 3: 4: # GET /articles 5: # GET /articles.json 6: def index 7: byebug => 8: @articles = Article.find_recent 9: 10: respond_to do |format| 11: format.html # index.html.erb 12: format.json { render json: @articles } (byebug) 


The arrow points to the line that starts the REPL instance. You can start executing your code right from it. At that point, @articles not yet been defined, but you can call Article.find_recent and see what happens. If an error occurs, you can type next and go to the next line in the same context. Or type step and execute the following instruction.



This is convenient, especially if you are trying to understand why the code does not display what you need. You can check the operation of each value in this context, and then copy the working code to the file regardless of the result of the execution. Incredibly convenient for testing.



Unless



Many don't like unless . They are often abused, as are many other things in many other languages. Unless does not give people peace of Unless for many years, as noted in one article from 2008.



The structure of unless is an antonym for if . The code is blocked if the condition is true , and continues to execute when the value is false .



 unless foo? # bar that thing end # Or def something return false unless foo? # bar that thing end 


This somewhat facilitates the work, especially with a large number of conditions. || and parentheses. Instead of this line:



 if ! (foo || bar) && baz 


can write:



 unless (foo || bar) && baz 


It may be too much, and no one will allow you to use unless along with else , but by itself it is a handy tool. Its implementation in PHP was requested back in 2007, but for a while the request was ignored. Finally, Rasmus Lerdorf, the creator of PHP, said that the unless() function would violate backward compatibility, and this “will not be obvious to those for whom English is not native.”



“This is a strange word, essentially meaning“ no if ”, although logically it should be an analogue of how“ less (less) ”is the opposite of“ more ”, but the prefix“ un ”changes its meaning to the opposite (less - unless). ”


Controversial statement. When people read the word unless , they do not perceive its meaning as “the opposite less” only because of the prefixed un . Otherwise, we would read the uniqid() function and consider it the opposite of iqid() .



Predicate Methods



There are a number of interesting agreements in Ruby that are differently resolved in PHP. One of them is predicate methods, that is, methods with a boolean response type (boolean response type). Given that type types are not returned to Ruby, such methods can be a good help.



Many predicate methods are already built into Ruby, for example, object.nil? . In fact, this is an analogue of $object === nil in PHP. If you need to request something and not perform an action, then it is better to use include? instead of include .



You can also set your own predicate methods:



 class Rider def driver? !potential_driver.nil? && vehicle.present? end end 


Many PHP developers add the prefix is and / or has to the name when creating the predicate method, for example, isDriver() or hasVehicle() . But sometimes you can come across other prefixes. Suppose the method can_drive? from Ruby can turn into canDrive() in PHP, and it will not be entirely clear that this is a predicate method. It is better to rename it, in accordance with tradition, into something like isAbleToDrive() .



Even more concise array syntax



In PHP, you can easily define literal arrays, and starting with PHP 5.4, an even more concise syntax appeared:



 // < 5.4 $a = array('one' => 1, 'two' => 2, 'three' => 'three'); // >= 5.4 $a = ['one' => 1, 'two' => 2, 'three' => 'three']; 


Someone might say that in this case, with brevity, they went overboard a bit . In Ruby 1.9, an option appeared that allows you to replace => with a semicolon. In PHP, one could go even further:



 $a = ['one': 1, 'two': 2, 'three': 'three']; 


In some cases it would be really convenient, for example, when it is necessary to fill nested arrays several hundred times a day. The proposal to simplify the syntax was posted in 2011, but still not accepted. It is possible that they will not accept it. In PHP, they try to preserve syntax minimalism and very rarely introduce new ways of implementing old things, even if the new product promises many advantages. It can be said that syntactic cakes are not a priority for the PHP team, while in Ruby this is almost the main direction.



Object Literals



Many Ruby developers would like to see this functionality in Ruby. If here you need to define a class StdClass with values, then you have two ways:



 $esQuery = new stdClass; $esQuery->query = new stdClass; $esQuery->query->term = new stdClass; $esQuery->query->term->name = 'beer'; $esQuery->size = 1; //  $esQuery = (object) array( "query" => (object) array( "term" => (object) array( "name" => "beer" ) ), "size" => 1 ); 


It has always been like this in PHP, but it can be done much easier. Let's say almost completely borrowing the syntax from Ruby:



Php

 $esQuery = { "query" : { "term" : { "name" : "beer" } }, "size" : 1 }; 


Ruby

 esQuery = { "query" : { "term" : { "name" : "beer" } }, "size" : 1 } 


I'd love to see this innovation in PHP, but so far there is no interest from the developers.



Rescue method



There is try/catch in PHP and begin/rescue in Ruby. They work almost identically, especially in light of the PHP 5.6 finally appearing, as an analogue of ensure from Ruby. In both languages, there is the possibility of recovery after exclusion from any place, for which the try and begin commands are used. But Ruby does much more: you can skip the begin method and recover directly from the function / method body:



Ruby

 def create(params) do_something_complicated(params) true rescue SomeException false end 


If something goes wrong, you can somehow handle the error, and not get angry and do not call someone to figure it out. This is not a panacea, but it is better to have different possibilities, without having to wrap everything in begin .



Unfortunately, this approach does not work in PHP, but if it were implemented, the code could look something like this:



 function create($params) { do_something_complicated($params); return true; } catch (SomeException $e) { return false; } 


At first glance, the thing is not too important. But it is precisely these many small amenities in Ruby that create the feeling that the language is really trying to help you, to be useful.



Retry after exception



A very convenient tool is the retry command:



 begin SomeModel.find_or_create_by(user: user) rescue ActiveRecord::RecordNotUnique retry end 


In this example, the competition between languages ​​is intensified again due to the non-atomicity of find_or_create_by (ORM first performs a SELECT and then INSERT ). And if you're unlucky, another process can create an entry after SELECT , but before INSERT .



Once this can happen, it is better to use SELECT to consider the possibility of begin...rescue . Perhaps in some cases you even want to put some logic here to drive it one or two times, but we will not consider this option. Let's better appreciate the convenience of re-execution of a piece of code. In Ruby, you can do this:



 def upload_image begin obj = s3.bucket('bucket-name').object('key') obj.upload_file('/path/to/source/file') rescue AWS::S3::UploadException retry end end 


And in PHP, you first need to create a new function / method for the contents of the initial block:



 function upload_image($path) { $attempt = function() use ($path) { $obj = $this->s3->bucket('bucket-name')->object('key'); $obj->upload_file($path); }; try { $attempt(); } catch (AWS\S3\UploadException $e) $attempt(); } } 


In both cases, you may want to use some kind of template blanks to stop the cycle. But from the point of view of re-execution, Ruby code is much cleaner. Rumor has it that active work is now underway on this functionality. Perhaps we will see it in PHP version 7.1.



A few thoughts at last



Not so long ago, I noticed for myself that I was writing in Ruby the same way as in PHP. But working together with strong and experienced Ruby developers taught me a number of characteristic, somewhat different approaches. So in this article I reflected what I would have missed in this language if I returned to PHP, but not enough to keep me from returning. Many PHP haters ignore innovations introduced in PHP. And even if there are not any convenient and useful things typical of Ruby, but in PHP 7 there are many other very interesting features.



In recent years, PHP has noticeably improved in terms of consistency, in particular, due to uniform variable syntax , context-sensitive lexer and abstract syntax tree . All this allows PHP to be much more consistent, regardless of the consistency of the standard library.



Since the standard library is unlikely to undergo improvements in the foreseeable future, we should only welcome the appearance of new convenient features and syntactic techniques. Other languages ​​can experiment as much as they like, try different theories and novelties, giving food for news feeds of programmer resources. And PHP will borrow exactly what this language needs. This is an effective approach. Can be considered as a pirate-marauder PHP .



If you have the time and desire, then experiment with a variety of languages. Ruby can be a good start, Go can be quite funny, and Elixir is confused, but fascinating. But this is not a call to rush from one to another. You can give each language enough time to get a taste, get used to it and bring your mind to tone.

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



All Articles