emGee Software Solutions Custom Database Applications

Share this

Planet PHP

People blogging about PHP
Updated: 5 days 3 hours ago

A rant about best practices - Stefan Koopmanschap

Tue, 01/09/2018 - 23:30

I have yet to talk to a developer that has told me that they were purposefully writing bad software. I think this is something that is part of being a developer, that you write software that is as good as you can possibly make it within the constraints that you have.

In our effort to write the Best Software Ever (TM) we read up on all the programming best practices: design patterns, refactoring and rewriting code, new concepts such as Domain-Driven Design and CQRS, all the latest frameworks and of course we test our code until we have a decent code coverage and we sit together with our teammates to do pair programming. And that's great. It is. But it isn't.

In my lightning talk for the PHPAmersfoort meetup on Tuesday, January 9th, 2018, I ranted a bit about best practices. In this blog post, I try to summarize what I ranted about.

Test Coverage

Test coverage is great! It is a great tool to measure how much of our code is being touched by unit (and possibly integration) tests. A lot of developers I talk to tell me that they strive to get 100% code coverage, 80% code coverage, 50% code coverage or any other arbitrary percentage. What they don't mention is whether or not they actually look at what they are testing.

Over the years I have encountered so many unit tests that were not actually testing anything. They were written for a sole purpose: To make sure that all the lines in the code were "green", were covered by unit tests. And that is useless. Completely useless. You get a false sense of security if you work like this.

There are many ways of keeping track of whether your tests actually make sense. Recently I wrote about using docblocks for that purpose, but you can also use code coverage to help you write great tests. Generating code coverage can help you identify which parts of your code are not covered by tests. But instead of just writing a test to ensure the line turns green, you need to consider what that line of code stands for, what behavior it adds to your code. And you should write your tests to test that behavior, not just to add a green line and an extra 0.1% to your code coverage. Code coverage is an indication, not a proof of good tests.

Domain-driven design

DDD is a way of designing the code of your application based on the domain you're working in. It puts the actual use cases at the heart of your application and ensures that your code is structured in a way that makes sense to the context it is running in.

Domain-Driven Design is a big hit in the programming world at the moment. These days you don't count anymore if you don't do DDD. And you shouldn't just know about DDD or try to apply it here and there, no: ALL YOUR CODES SHOULD BE DDD!1!1shift-one!!1!

Now, don't get me wrong: There is a lot in DDD that makes way more sense than any approach I've used in the past, but just applying DDD on every bit of code you write does not make any sense. Doing things DDD is not that hard, but doing DDD right takes a lot of learning and a lot of effort. And for quite a few of the things that I've seen people want to use full-on DDD recently, I wonder whether it is worth the effort.

So yes, dig into DDD, read the blue book if you want, read any book about it, all the blog post, and apply it where it makes sense. Go ahead! But don't overdo it.

Frameworks

I used to be a framework zealot. I was convinced that everyone should use frameworks, and everyone should use it all the time. For me it started with Mojavi, then Zend Framework and finally I settled on Symfony. To me, the approach and structure that Symfony gave me made so much sense that I started using Symfony for every project that I worked on. My first step would be to download (and later: install) Symfony. It made my life so much easier.

Using a framework does make a lot of sense for a lot of situations. And I personally do not really care what framework you use, although I see a lot of people saying "You use Laravel? You're such a n00b!" or "No, you have to use Symfony for everything" or "Zend Framework is the only true enterprise framework and you need to use it".

First of all: There is no single framework that is good for every situation. Second of all, why use a pre-fab framework when you can build your own?. And sometimes you really don't need a framework. Stop bashing o

Truncated by Planet PHP, read more at the original (another 6971 bytes)

Categories: Web Technologies

What version of PHP should my package support? - Brandon Savage

Tue, 01/09/2018 - 06:00

Everybody likes “the new hotness.” Everyone loves a new car, or a new computer, or the state-of-the-art video gaming console. It’s why people camp out for days to get their hands on a new iPhone, when they could just buy one the next week off the shelf. People love to have the hot thing, right […]

The post What version of PHP should my package support? appeared first on BrandonSavage.net.

Categories: Web Technologies

PHP 5.6.33 Released - PHP: Hypertext Preprocessor

Wed, 01/03/2018 - 16:00
The PHP development team announces the immediate availability of PHP 5.6.33. This is a security release. Several security bugs were fixed in this release. All PHP 5.6 users are encouraged to upgrade to this version.For source downloads of PHP 5.6.33 please visit our downloads page, Windows source and binaries can be found on windows.php.net/download/. The list of changes is recorded in the ChangeLog.
Categories: Web Technologies

PHP 7.1.13 Released - PHP: Hypertext Preprocessor

Wed, 01/03/2018 - 16:00
The PHP development team announces the immediate availability of PHP 7.1.13. This is a security release. Several security bugs were fixed in this release.All PHP 7.1 users are encouraged to upgrade to this version.For source downloads of PHP 7.1.13 please visit our downloads page, Windows source and binaries can be found on windows.php.net/download/. The list of changes is recorded in the ChangeLog.
Categories: Web Technologies

PHP 7.2.1 Released - PHP: Hypertext Preprocessor

Wed, 01/03/2018 - 16:00
The PHP development team announces the immediate availability of PHP 7.2.1. This is a security release. Several security bugs were fixed in this release.All PHP 7.2 users are encouraged to upgrade to this version.For source downloads of PHP 7.2.1 please visit our downloads page, Windows source and binaries can be found on windows.php.net/download/. The list of changes is recorded in the ChangeLog.
Categories: Web Technologies

Symfony 4: Unpack the Packs - Fabien Potencier

Wed, 01/03/2018 - 16:00

We get a lot of positive feedback on Symfony 4. You love the new directory structure. You love the simplicity of using services without configuring anything. And you love the automation that Flex provides. It makes me happy and proud.

But one issue I hear sometimes is that it is more complex to start a new project. Most projects need many Symfony features. But new projects now have bare minimum dependencies when created via composer create-project symfony/skeleton. You need to explicitly add all the dependencies you want to depend on. Being explicit is great, but UX suffers. Ever tried to add profiler and didn't get timing info? Yup, symfony/stopwatch is optional, so you need to add it explicitly.

You loved the simplicity of starting a project with the Symfony Standard Edition. Is it nostalgia? Perhaps. Would it be possible to get the best of both world? Certainly! Read on.

First, let me recap some current available features:

  • Any Composer package can have a related recipe that helps with auto-configuration (recipes are stored at symfony/recipes and symfony/recipes-contrib);

  • Symfony Packs are Composer metapackages that bundle several dependencies together to make your life easier (install symfony/debug-pack and get everything you need to debug a Symfony application).

Symfony Packs are time savers, but they have one limitation: they mask the real dependencies. Let me explain with an example. Using symfony/orm-pack is a great way to get the most commonly needed Doctrine related packages and bundles. Run composer req symfony/orm-pack (or simply composer req orm) to get Doctrine core, the Doctrine bundle, and the Doctrine migrations bundle (everything configured thanks to some nice recipes).

But what if you want to remove one dependency that is included in the pack? Like the migrations bundle. You cannot remove it as the project's composer.json file requires symfony/orm-pack, not the individual dependencies:

diff --git a/composer.json b/composer.json index e24f26b..5238f98 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "symfony/flex": "^1.0", "symfony/framework-bundle": "^4.0", "symfony/lts": "^4@dev", + "symfony/orm-pack": "^1.0", "symfony/yaml": "^4.0" }, "require-dev": {

Another example would be when you want to change a dependency constraint for a package coming from a pack.

You can of course require the individual dependencies, but for packs like debug or api, you would have to have a look at their composer.json on Github and do some copy/paste. Not ideal.

There is another way. Unpacking the pack. You can now unpack an already installed pack via the unpack command:

composer unpack orm

The command updates composer.json to remove the pack and replace it with the individual dependencies defined in the pack:

diff --git a/composer.json b/composer.json index 5238f98..b8c9794 100644 --- a/composer.json +++ b/composer.json @@ -3,11 +3,13 @@ "license": "proprietary", "require": { "php": "^7.1.3", + "doctrine/doctrine-bundle": "^1.6.10", + "doctrine/doctrine-migrations-bundle": "^1.3", + "doctrine/orm": "^2.5.11", "symfony/console": "^4.0", "symfony/flex": "^1.0", "symfony/framework-bundle": "^4.0", "symfony/lts": "^4@dev", - "symfony/orm-pack": "^1.0", "symfony/yaml": "^4.0" }, "require-dev": {

Tweaking the pack dependencies is now possible. Don't want the migration bundles? Simple enough:

composer rem migrations

You can also unpack a pack at installation time via the --unpack flag. That flag tells Composer to add the dependencies of the pack in your composer.json instead of adding the pack package itself:

composer req orm-pack --unpack

Note that the unpack command and the --unpack flag only work for Symfony packs (the Composer package type must be symfony-pack). Any other dependencies are simply ignored and follow the standard Composer installation process.

That's a great feature by itself and give you even more power when it comes to dependency management.

Now, you could create a "Symfony Standard Edition" pack and benefit from the new unpacking feature.

That would almost work. Except that the Symfony Standard Edition has dev dependencies that would not be installed by Composer. As you know, when

Truncated by Planet PHP, read more at the original (another 3038 bytes)

Categories: Web Technologies

Using Composer packages with OpenWhisk - Rob Allen

Wed, 01/03/2018 - 03:03

When creating new OpenWhisk actions in PHP, It's likely that you'll want to take advantage of the rich ecosystem of Composer packages on Packagist.org.

The OpenWhisk PHP runtime has you covered with some pre-installed Composer packages and also the ability to upload your own using a zip file.

Pre-installed Composer packages

The PHP runtime ships with two Composer packages by default: Guzzle and ramsey/uuid.

This is handy as if you need to make an API call from your action, then you have the full power to Guzzle at your fingertips. The ramsey/uuid library is also handy for those times when you need a UUID when PUT'ing to that API.

If you need anything else then you have to do a bit more leg work.

Uploading your own project files

The PHP runtime also accepts a zip file when creating or updating an action. This means that you can create a PHP project for your action that includes a composer.json.

For example, let's say that you want to use NFNumberToWord in your action.

Create the PHP project

Firstly, create the composer.json file:

$ composer require nineteenfeet/nf-number-to-word

This creates composer.json, composer.lock and a vendor directory with the component and autoloader code in it.

We now need an action file. This must be called index.php:

index.php:

<?php use NFNumberToWord\NumberToWords; function main(array $args) : array { if (!isset($args['number'])) { return ['error' => 'Please supply a number.']; } $number = (int)($args['number']); $words = (new NumberToWords)->toWords($number); return [ 'result' => "$number in words is: $words", 'words' => $words, ]; }

The main function takes an associative array of arguments and must return an associative array which is converted to JSON for us. As with any other library, we import it using a use statement and can then instantiate the class and call toWords() on it.

Note that we don't need to include the Composer autoload file as this is done automatically by the PHP runtime.

Upload to OpenWhisk

To upload to OpenWhisk we first zip up the files and then use the wsk command line client to create the action:

$ zip -q -r n2w.zip index.php vendor

The name of the zip file is immaterial, so I've used something simple. Now we upload it to OpenWhisk:

$ wsk action update n2w n2w.zip --kind php:7.1

In this case, I've named the action n2w and passed in the zip file we have just created and specified the runtime to use with the --kind parameter. This is needed because we are supplying a zip file as our action code and OpenWhisk need to know which runtime to use for it.

That's it. We're done.

Running our action

We can prove it worked using:

$ wsk action invoke -r n2w -p number 12345

This outputs:

{ "result": "12345 in words is: twelve thousand, three hundred and forty-five", "words": "twelve thousand, three hundred and forty-five" }

Fin

As you can see, it's really easy to take advantage of the vast library of Packagist components in your actions, but of course, if all you need is an HTTP client, then Guzzle is already available.

Categories: Web Technologies

Generators: All About the Yield - Nomad PHP

Tue, 01/02/2018 - 16:41

March 2018 - EU
Presented By
Justin Yost
March 22, 2018
20:00 CET

The post Generators: All About the Yield appeared first on Nomad PHP.

Categories: Web Technologies

Considering Typehints As Communication - Paul M. Jones

Tue, 01/02/2018 - 07:51

Typehints help communicate across time and space, to people who may never meet you or who might not be able to interrogate you about your code, so those people can understand how you expect the code to work.

Adding typehints is a succinct, more-complete form of communication than not-adding them. (It is rare, perhaps impossible, for all communication can be fully complete all the time.)

Further, you don’t know in advance which parts of the codebase are going to last for a long time, and which are going to be replaced in relatively short order. It’s probably better to to add the typehints when you know what they are, rather than to wait and see if you’ll “need” them later.

Typehints can be considered low-cost mistake-proofing against misunderstanding in an obvious place (i.e., where they are used), without having to look elsewhere (“just read the tests!” [groan]).

Categories: Web Technologies

Solving The “Widget Problem” In ADR - Paul M. Jones

Thu, 12/28/2017 - 08:15

The “widget problem” is when you have several panels or content areas on an html page that have different data sources. You might have a main content area, then a calendar off to the side, with perhaps a list of recent news items or blog posts, a todo or reminder widget, and maybe other information panels. The problem is that they each have different data sources, and may not always be displayed in every circumstance — perhaps they are only shown to some users based on their preferences, or under certain conditions.

So how, in Action-Domain-Responder, do we get the “right” data for the set of widgets that are actually going to be displayed? (We’ll presume here that the entire page is being rendered server-side, for delivery as a whole to the client.)

The answer is “the same as with anything else” – we just have more kinds of data to get from the domain. The domain has all the data needed, and the knowledge necessary to figure out which data elements to return.

Let’s start with the Action, which is intentionally very spare: it only collects input, calls the Domain with that input, then invokes the Responder:

<?php class PageAction { public function __construct( PageService $domain, PageResponder $responder ) { $this->domain = $domain; $this->responder = $responder; } public function __invoke(HttpRequest $request) { $payload = $this->domain->fetchPageData( $request->getAttribute('pageName'), $request->getAttribute('sessionId') ); return $this->responder->respond($request, $payload); } }

The domain work is where the heavy lifting happens. The example below returns the domain objects and data wrapped in a Domain Payload object.

<?php class PageService { // presume $userService, $sessionService, and $database // dependencies are injected via constructor public function fetchPageData($sessionId, $pageName) { $session = $this->sessionService->resume($sessionId); $user = $this->userService->fetch($session->userId); // the main page data $mainData = $this->fetchMainData($pageName); if (! $mainData) { return new Payload('NOT_FOUND'); } // an array of widgets to show $widgets = []; // different users might prefer to see different widgets foreach ($user->getWidgetsToShow() as $widgetName) { $method = "fetch{$widgetName}Data"; $widgets[$widgetName] = $this->$method(); } $this->sessionService->commit($session); return new Payload('FOUND', [ 'user' => $user, 'page_name' => $pageName, 'main_data' => $mainData, 'widgets' => $widgets ]); } protected function fetchMainData($page_name) { return $this->database->fetchRow( "SELECT * FROM pages WHERE page_name = ? LIMIT 1", $page_name ); } protected function fetchTodoData() { ... } protected function fetchRemindersData() { ... } protected function fetchUpcomingEventsData() { ... } protected function fetchCalendarData() { ... } }

Finally, the Responder work becomes as straightforward as: “Is there Todo data to present? Then render the Todo widget via a Todo helper using the Todo data.” It could be as simple as this:

<?php class PageResponder { // ... public function respond(Request $request, Payload $payload) { if ($payload->getStatus() == 'NOT_FOUND') { return new Response(404); } $output = $payload->getOutput(); $html = ''; $html .= $this->renderHeader($output['page_name']; $html .= $this->renderNav(); $html .= $this->renderMain($output['main_data']); foreach ($output['widgets'] as $widgetName => $widgetData) { $method = "render{$widgetName}Html"; $html .= $this->$method($widgetData); } return new Response(200, $html); } protected function renderHeader($request, $pageName) { ... } protected function renderNav($request) { ... } protected function renderMain($request, $mainData) { ... } protected function renderTodoHtml($request, $widgetData) { ... } protected function renderRemindersHtml($request, $widgetData) { ... } protected function renderUpcomingEventsHtml($request, $widgetData) { ... } protected function renderCalendarHtml($request, $widgetData) { ... } ?>

One alternative here is for some client-side Javascript to make one additional call per widget or panel to retrieve widget-specific data, then render that data on the client. The server-side work becomes less complex (one action per widget, and transform the data to JSON instead of html) – but the client-side work becomes more complex, and you have more HTTP calls back-and-forth to build the page.

Categories: Web Technologies

A Little Christmas Gift - thePHP.cc

Thu, 12/21/2017 - 23:00
Categories: Web Technologies

Banishing Loops with Functional Programming - Nomad PHP

Thu, 12/21/2017 - 21:01

March 2018 - US
Presented By
David Hayes
March 22, 2018
20:00 CDT

The post Banishing Loops with Functional Programming appeared first on Nomad PHP.

Categories: Web Technologies

Saying Thanks – Open Source Appreciation - blog.phpdev

Thu, 12/21/2017 - 07:11

Wow, it has been a really long time since I’ve posted here. Most of my writing has ended up in articles of php[architect] or over on Websec.io. I wanted to jump back into the blog though and talk about something inspired by a post over on the Symfony blog about giving thanks.

Normally the Thanksgiving holiday is more associated with sharing what you’re thankful for in your life. However, the Christmas holiday comes at a perfect time to look back over the past year and think about everything you’re thankful for and how your life has changed, hopefully for the better. In the Symfony blog post they share a Composer plugin – Thanks – that makes it simple to show your appreciation to those packages you’re currently using in your applications. It sends stars to the projects (the ones you haven’t already starred naturally) as a token of appreciation. This is a great first step to showing how much you appreciate the work the maintainers have done but there’s also other ways to show that you support them and that they’re doing a great job. Here’s a few other suggestions:

  • Find their email (often in the repo’s README.md file) and send them a quick note telling them how much you enjoy using the library and how important it is to your application.
  • Write a blog post about the package and how you use it to share with others. Maintainers always enjoy seeing how their work is used and maybe even get ideas for future features and fixes.
  • Send out a Tweet about the project, telling them how much you appreciate the work they do.
  • Support them on Patreon if they have a page on there with a recurring donation. This allows them to spend more time focusing on the project and making it better for everyone.
  • Open an issue on the project’s Github/Bitbucket repository with a quick “Thanks” to everyone involved.

These are just a few ideas to get you started, of course. There’s plenty of other ways that you can support and thank the authors and maintainers of your favorite packages. Get creative and think of your own but this is the perfect time of year to do it and let those developers know their work is appreciated!


Categories: Web Technologies

Faculty - Chris Shiflett

Mon, 12/11/2017 - 23:00

Today, I am proud to introduce Faculty. We design, engineer, and build really great websites and apps.

I love the web, and I feel fortunate to have been working on the web since its inception. From writing CGIs in C and laying out pages with tables, to CSS Grid and serverless computing, it has always been fulfilling to bring new ideas to life and share them with others.

I also love working with smart, friendly people who obsess over the details as much as I do, and I’m grateful to those who are continuing this journey with me. We’re an experienced team, and we enjoy using our experience to unlock the potential of our clients.

Faculty is the culmination of everything I’ve learned about technology, design, and business. If you think we can help you, please do let us know, and thanks in advance for your support.

Categories: Web Technologies

Symfony 4: Performance out of the Box - Fabien Potencier

Sun, 12/10/2017 - 16:00

Performance is an interesting and sensitive topic. Suffice to say that most projects should not care too much; modern PHP frameworks are fast enough for most use cases and projects. And PHP 7 performance improvements help a lot as well. But people like to compare frameworks, and I guess performance is one way to do so.

What about Symfony 4? During its development cycle, we did some nice performance optimizations: mainly to improve the router and container speed. But several non-related changes in Symfony 4 also help with performance... Your mileage may vary depending on your specific project of course.

Synthetic benchmarks that were made to back pull requests that claimed to improve performance do not always convert to gain on real-world projects. Now that Symfony 4 is out, it's time to test a full project. An "Hello World!" application benchmark is a good start as it helps understand the base performance of a framework. It helps understand the overhead added on top of PHP by your framework of choice. We spent so much time doing optimizations that I wanted to compare the out-of-the-box performance of a default Symfony Standard Edition project (based on Symfony 3) with the performance of a Symfony 4 project.

Benchmarking is a science and takes a lot of time. Luckily enough, and while I was procrastinating, someone did the work for me. The http://www.phpbenchmarks.com/en website has a great benchmark protocol and a great UI that let you browser and compare results easily.

According to those benchmarks, an "hello world" page on Symfony 4.0 is almost twice as fast as the same code using Symfony 3.4. You read that right. On PHP 7.2, Symfony 4.0 is twice as fast compared to Symfony 3.4.

There is also a benchmark for a more complex REST API application. Symfony 4 is again still much faster than 3.4.

Visit http://www.phpbenchmarks.com/en to get more numbers. Among other things, you will learn the following:

  • Symfony on PHP 7.2 makes your code quite a bit faster than 7.1;

  • Symfony 2.3 is the second fastest Symfony release since 2.0

  • Symfony 3.4 is the slowest release since 2.0 (deprecated features are probably one of the reasons);

  • Symfony 4.0 is almost three times as fast as Laravel 5.5.

I would love to get some numbers for your application. Share them if you upgraded recently.

Enjoy using Symfony 4.0, the fastest Symfony ever released!

Categories: Web Technologies

Whatsapp Chat from CLI Using Puppeteer - Sarfraz Ahmed

Sun, 12/10/2017 - 03:06

GoogleChrome puppeteer is interesting project to create browser automation tools, testing tools or web scrapping. I was wondering to use it for something useful and an idea popped in about creating CLI application that can be used to send and receive messages on Whatsapp. So I went on to creating Whatspup, a name based on both Whatsapp and puppeteer.

Features
  • Send and receive messages
  • Read Receipts
  • Switch between users to chat with
  • Popup notifications for new chat messages
  • Privacy settings for popup notifications
  • One-time authentication, no need to scan QR code again and again
  • Windowless/headless (hidden) mode
  • Colorful chat messages
Screenshot

Head over to Whatspup repository and start using it :)

Categories: Web Technologies

Avoid Dependency Injection - Paul M. Jones

Tue, 12/05/2017 - 05:00

At least, avoid it when building DDD Aggregates:

Dependency injection of a Repository or a Domain Service into an Aggregate should generally be viewed as harmful. The motivation may be to look up a dependent object instance from inside the Aggregate. The dependent object could be another Aggregate, or a number of them. … Preferably, dependent objects are looked up before an Aggregate command method is invoked, and passed into it.

… Take great care not to add unnecessary overhead that could be easily avoided by using other design principles, such as looking up dependencies before an Aggregate command method is invoked, and passing them into it.

This is only meant to warn against injecting Repositories and Domain Services into Aggregate instances. Of course, dependency injection is still quite suitable for many other design situations. For example, it could be quite useful to inject Repository and Domain Service references into Application Services.

— “Implementing Domain Driven Design”, Vaughn Vernon, p 387.

On a related note, regarding where Entity validation logic goes, we have this …

Validation is a separate concern and should be the responsibility of a validation class, not a domain object.

— ibid., p 208

… and this …

Embedding validation logic inside an Entity give it too many responsibilities. It already has the responsibility to address domain behavior as it maintains its state.

— ibid., p 212

… but then we see this:

How do clients ensure that Entity validation occurs? And where does validation processing begin? One way places a validate() method on all Entities that require validation. … Any Entity can safely have its validate() method invoked. …

However, should Entities actually validate themselves? Having its own validate() method doesn’t mean the Entity itself performs validation. Yet, it does allow the Entity to determine what validates it, relieving clients from that concern:

public class Warble extends Entity { ... @Override public void Validate(ValidationNotificationHandler aHandler) { (new WarbleValidator(this, aHandler)).validate(); } }

… The Entity needs to know nothing about how it is validated, only that it can be validated. The separate Validator subclass also allows the validation process to change at a diferent pace from the Entity and enables complex validations to be thoroughly tested.

— ibid., p 214-215

It seems like a small step after that to inject a fully-constructed validation object into the Entity at construction time, and have the validate() method call that, instead of creating a new validation object inside the Entity.

Categories: Web Technologies

PHP 7.2.0 Released - PHP: Hypertext Preprocessor

Wed, 11/29/2017 - 16:00
The PHP development team announces the immediate availability of PHP 7.2.0. This release marks the second feature update to the PHP 7 series.PHP 7.2.0 comes with numerous improvements and new features such asConvert numeric keys in object/array castsCounting of non-countable objectsObject typehintHashContext as ObjectArgon2 in password hashImprove TLS constants to sane valuesMcrypt extension removedNew sodium extensionFor source downloads of PHP 7.2.0 please visit our downloads page Windows binaries can be found on the PHP for Windows site. The list of changes is recorded in the ChangeLog.The migration guide is available in the PHP Manual. Please consult it for the detailed list of new features and backward incompatible changes.Many thanks to all the contributors and supporters!
Categories: Web Technologies

Pages