📜 ⬆️ ⬇️

New life legacy project

On Habré there are many articles how to do well. There are many articles on how to do not worth it.
But what if you have a project that works, brings money to business, but from programmers, almost no one is able to understand something in it. Each change is made at the fear and risk of playing Russian roulette.

It is good when you have a year or two to prepare a new project from scratch (I also had this experience). But if the owner is not ready to invest in the fact that you will do something in parallel from scratch, while the old engine is completely bent. Attempts to even speak, about how to write everything in a new way, meet an instant failure. But you can try in small steps, make a new engine, which would gradually begin to take over all the big and big role in the project. Actually about the experience of such a replacement, I would like to tell you.

Given


The site for selling tickets to concerts, php plus muskul, the code is old, has a slight logical separation (for my taste, a bit better Bitrix), templates, business logic and direct requests in the database directly in one method. The most used classes are on 2,000+ lines. In general, everything we love.

Where are we moving? Symphony, as it is the most popular framework in our environment, there are many people who have spent more than one year in creating projects on it. Also, the symphony is now used in many projects, has good documentation and uses many new pieces of php. In general, the odes to this framework are sung in many articles, but in practice it comes down to easier support and the ability to search for specialists.
')

Creating models


Data is the most important thing that you have, especially if you want to “throw out” the engine. Therefore, it is logical to start the development of a new engine with picking up data from the current database. In the case of symfony, we will create models. In fact, everything is quite simple here, you can create basic fields using the doctrine: mapping: import command (you can read more here ). In order not to process all the tables at once, the team was launched on a trimmed base, where only the main tables remained (orders, tickets, events and users, plus a pair of link tables and extended data)

Here you can meet the first problems. Our database turned out to be enum, which the doctrine does not want to digest, but within a couple of hours it turned out to create a migration (here we mean just an sql file, not a migration of the doctrine) with our small ten of these fields, which replaced the enum with strings / numbers.

Having worked a bit with the columns, we can easily get connections in our objects. Unless of course the old developers took care of you and used the primary key as a connection or some kind of columns with which you could describe the one / many connections. So here we are a bit lucky.

Almost lucky. The second problem turned out to be with orders, the word order is reserved, in one of the tables, there is a column that contains the order id in this field (thanks to the project author that in other tables he suddenly began to use ord_id and not the word order). Here from the quick solutions was only a hack with the download.

public function postLoad(LifecycleEventArgs $event) { $entity = $event->getEntity(); if ($entity instanceof Ticket) { // getOrderNumber       order $id = $entity->getOrderNumber(); if ($id) { $order = $event->getEntityManager()->getRepository('AppBundle:Orders')->find($id); if (!$order) { throw new \Exception(sprintf("Order %d not found in base", $id)); } // setOrder/getOrder        orderObject       $entity->setOrder($order); } } } 


After discussing the options in the comments, the user myLizzarD pushed us to another version of the hack, this time with a full connection.

  /** *   * @ORM\Table(name="`orders`") */ /** *    * @ORM\ManyToOne(targetEntity="Orders") * @ORM\JoinColumn(name="order", referencedColumnName="id", nullable=true) */ private $order; //          public function preUpdate(PreUpdateEventArgs $args) { $entity = $args->getEntity(); if ($entity instanceof Ticket) { if ($changes = $args->getEntityChangeSet()) { if (isset($changes['order'])) { $em = $args->getEntityManager(); $uow = $em->getUnitOfWork(); $metadata = $em->getClassMetadata(Ticket::class); //  ,        $data = $uow->getOriginalEntityData($entity); $data['order'] = $changes['order'][1]; $uow->setOriginalEntityData($entity, $data); // ,    order $query = sprintf( 'UPDATE `%s` SET `order` = %s WHERE id = %d', $metadata->table['name'], $changes['order'][1] instanceof Orders ? $changes['order'][1]->getId() : 'null', $entity->getId() ); $em->getConnection()->executeQuery($query); //    ,        // ,      ,     $uow->clearEntityChangeSet(spl_object_hash($entity)); $uow->recomputeSingleEntityChangeSet($metadata, $entity); } } } } 

Problem three arose with migrations and defaults, which also had to be entered by hand, as well as duplicated for the symphony, so we got something like this.

 /** * @ORM\Column(type="integer", options={"default" = 0}) */ public $type = 0; 

Crown replacement


How to start to introduce a new engine? What is not scary to break without losing customers, if your project is not covered by tests? Where is the "dirty" work, which is not particularly interesting to business holders? Where can you implement the logic, but you do not need to integrate the templates of the old site? So we have a choice on the baptism of the new engine fell on the crowns of the task. At least one more reason for which he wanted to be replaced, is that in the old engine the calls went through wget.

Yes, at this stage we caught the first problems, but it’s better that our tickets are in the reserve more than the people can’t buy at all.

First steps (separate widget)


The second block, which was implemented on a new engine, is an unexpectedly turned up widget for partners. From himself he represented a small page, which through the iframe is embedded on the partner site. And also this page required separate management in the admin panel. Honestly, when I read the requirements and realized that the new engine had just passed a baptism of fire, I thought that it could not be more perfect.

There are no problems with the iframe itself, the layout is separate, the template is separate, even a separate table for managing what needs to be shown. But all this was required to be edited through the admin panel, and most importantly, that the user did not feel the difference (or only felt for the better). Therefore, it was necessary to solve two problems: authorization and appearance of forms.

The first was decided through the mechanism of guard'ov . All it took was to transfer the logic from the old code to the getCredentials and getUser methods. For our project, all the logic took less than 10 lines. In my 5 years on the symphony, I have never had a faster setting up authorization and just one class. The question with the appearance is easily solved with the help of styling forms .

Nginx configuration


Now you need to run all this miracle. On the old project, a bunch of nginx + apache was used, and there are some problems here that few people really can configure Apache configs with path splits, so a second backend with php-fpm was organized. New paths were sent to app.php and he, in turn, was sent to fpm with a root in the folder of the new project. Actually about such code was added to a config.

 location ~ ^/admin/parnter { root /path-to-new-engine/web; try_files $uri /app.php$is_args$args; } location ~ ^/app\.php(/|$) { root /path-to-new-engine/web; Internal; ...fast_cgi config... } 

What is the result?


At the moment, we can say that we have taken a step from the concept to the things that are already working. In general, a positive experience was obtained, and the problems that have arisen at this stage can hardly be called problems, rather, they are programmer days. Just do not forget about the psychological effect, now this is not some kind of legacy project that has no end to it, there are already prospects for the introduction of new developments, which undoubtedly attracts developers. The customer also had a positive effect, since we started using a more well-known and predictable tool on his project, then for some of his customers we began to reduce the deadlines, which also affected the reduction of the cost of developing new chips. Not everyone, of course, but with something to start.

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


All Articles