Teaches you to write modern PHP code without the use of frames

Source: Internet
Author: User
Tags autoload autoloader install php composer install zend zend framework

I have prepared a challenging thing for you. Next you will start a project tour in a non -framed way.

First of all, this is not a smelly and long anti-frame binding cloth article. It's not about selling non-original ideas. After all, we'll also use the other framework developers ' support packages for the next development journey. I also have an indisputable attitude towards innovation in this field.

It's not about others, it's about your own body. As a developer, it will give you a chance to grow.

Perhaps the most rewarding aspect of without framework development is that you can learn a lot from the underlying workings. By giving up the framework that relies on magic to help you deal with things that you can't debug and don't really understand, you'll see clearly how this all happened.

It is possible that you will not be able to choose a framework to develop new projects in your next job. The reality is that existing applications are used in many high-value, business-critical PHP jobs. And whether the application is built in popular frameworks such as the current Laravel or Symfony, or obsolete codeigniter or fuelphp, and perhaps more broadly in a frustrating "inclusive architecture" of traditional PHP applications , so no framework development will help you in any future PHP projects you face.

In ancient times, because some systems had to interpret the distribution of HTTP requests, send HTTP responses, manage dependencies, no framework development was a painful battle. The lack of industry standards necessarily means that these components in the framework are highly coupled. If you start without a frame, you will never escape the fate of your own framework.

Today, thanks to the php-fig to complete all the automatic loading and interactive work, no framework development does not let you from scratch. All kinds of vendors have so many excellent interoperable packages. It's easier to put them together than you think!

How does PHP work?

Before doing anything else, it's important to understand how PHP communicates with the outside world.

PHP runs the service-side application as a request/response cycle. Every interaction with your application-whether from a browser, a command line, or a REST api---enters the application as a request. When the request is received:

    1. The program starts to start;
    2. Start processing requests;
    3. Generate a response;
    4. The response is then returned to the corresponding client that generated the request;
    5. The final program is closed.

each request repeats more than one interaction.

Front Controller

Armed with this knowledge, you can start writing programs from our front-end controllers. The front controller is a PHP file that handles every request of the program. The controller is the first PHP file that is encountered after the request enters the program, and (in essence) the last file that goes through your application.

We use the classic Hello, world! as an example to make sure everything is connected correctly, this example is driven by PHP's built-in server. Before you start doing this, make sure you have installed PHP7.1 or later.

Create a public project that contains a directory, and then create a file within that directory index.php , and write the following code in the file:

<?phpdeclare(strict_types=1);echo ‘Hello, world!‘;

Note that here we declare the use of strict mode-as a best practice, you should do this at the beginning of every PHP file in your application. Because for developers who come from behind you, type hints are important for debugging and clear communication intentions.

Use the command line (such as MacOS terminal) to switch to your project directory and start PHP's built-in server.

php -S localhost:8080 -t public/

Now, open http://localhost:8080/in the browser. Did you succeed in seeing the "Hello, world!" output?

Very good. Then we can start to get to the point!

Automatic loading with third-party packages

When you use PHP for the first time, you may use the or statements in your includes program requires to import features and configurations from other PHP files. Usually, we avoid doing this because it makes it more difficult for others to follow your code path and understand where the dependencies are. This has made debugging a real nightmare.

The workaround is to use automatic loading (autoloading). The automatic loading means that when your program needs to use a class, PHP knows where to find and load it when calling that class. Although this feature can be used starting with PHP 5, the use of PSR-0 (auto-load standard, later replaced by PSR-4) has only started to have a real boost in utilization.

We can write our own autoloader to complete the task, but since the Composer we're going to use to manage third-party dependencies already contains a perfect available autoloader, we'll just use it.

Make sure you have Composer installed on your system. Then initialize the Composer for this project:

composer init

This command guides you through interactive creation of composer.json configuration files. Once the file is created, we can open it in the editor and write the field to it to autoload make it look like this (this ensures that the autoloader knows where to find the classes in our project):

{    "name": "kevinsmith/no-framework",    "description": "An example of a modern PHP application bootstrapped without a framework.",    "type": "project",    "require": {},    "autoload": {        "psr-4": {            "ExampleApp\\": "src/"        }    }}

Now that you have installed composer for this project, it introduces dependencies (if any) and creates a good autoloader for us:

composer install

Update the public/index.php file to introduce the autoloader. Ideally, this will be one of the few "include" statements you'll use in your program.

<?phpdeclare(strict_types=1);require_once dirname(__DIR__) . ‘/vendor/autoload.php‘;echo ‘Hello, world!‘;

At this point, if you refresh your browser, you will not see any changes. Because the autoloader does not modify or output any data, we see the same content. Let's move the example of Hello, world! to a class that's already loaded automatically to see how it works.

Create a directory in the project root directory src , and then add a named file inside it HelloWorld.php , and write the following code:

<?phpdeclare(strict_types=1);namespace ExampleApp;class HelloWorld{    public function announce(): void    {        echo ‘Hello, autoloaded world!‘;    }}

Now go public/index.php inside HelloWorld and replace the statement with the method of the class announce echo .

// ...require_once dirname(__DIR__) . ‘/vendor/autoload.php‘;$helloWorld = new \ExampleApp\HelloWorld();$helloWorld->announce();

Refresh your browser to view new information!

What is dependency injection?

Dependency injection is a programming technique in which each dependency supplies the object it needs, rather than getting the required information or functionality out of the object.

For example, suppose a class method in an application needs to be read from a database. To do this, you need a database connection. A common technique is to create a new, globally visible connection.

class AwesomeClass{    public function doSomethingAwesome()    {        $dbConnection = return new \PDO(            "{$_ENV[‘type‘]}:host={$_ENV[‘host‘]};dbname={$_ENV[‘name‘]}",            $_ENV[‘user‘],            $_ENV[‘pass‘]        );        // Make magic happen with $dbConnection    }}

But it's messy, it puts a responsibility that doesn't belong here---Create a database Connection object , check the credentials, and handle some connection failures---it causes a lot of * Duplicate code to appear in the app. If you try to unit test this class, you will find that it is not feasible at all. This class is highly coupled with the application environment and the database.

Instead, why not start figuring out what your class needs? We just need to inject the "PDO" object into the class first.

class AwesomeClass{    private $dbConnection;    public function __construct(\PDO $dbConnection)    {        $this->dbConnection = $dbConnection;    }    public function doSomethingAwesome()    {        // Make magic happen with $this->dbConnection    }}

This is more concise, understandable, and less prone to bugs. With type hints and dependency injection, the method can clearly and accurately declare what it is going to do without relying on external calls to get it. When we do the unit test, we can simulate the database connection very well and pass it into use.

The dependency Injection container is a tool that you can handle to create and inject these dependencies around the entire application. Containers do not need to be able to use dependency injection technology, but it can be beneficial as applications grow and become more complex.

We will use one of the most popular DI containers in PHP: a veritable php-di. (it is recommended that dependency injection in its documentation may be helpful to the reader)

Dependency Injection Container

Now that we've installed the Composer, it's easy to install PHP-DI, and we'll go back to the command line to get it done.

composer require php-di/php-di

Modifications are public/index.php used to configure and build the container.

// ...require_once dirname(__DIR__) . ‘/vendor/autoload.php‘;$containerBuilder = new \DI\ContainerBuilder();$containerBuilder->useAutowiring(false);$containerBuilder->useAnnotations(false);$containerBuilder->addDefinitions([    \ExampleApp\HelloWorld::class => \DI\create(\ExampleApp\HelloWorld::class)]);$container = $containerBuilder->build();$helloWorld = $container->get(\ExampleApp\HelloWorld::class);$helloWorld->announce();

It's no big deal. It's still a simple example of a single file, and it's easy to see how it works.

So far, we're just configuring the container, so we have to explicitly declare the dependency (instead of using automatic assembly or annotations) and retrieve the object from the container HelloWorld .

Tip: Automatic assembly is a great feature when you start building applications, but it hides dependencies and is difficult to maintain. It is likely that in the next few years, another developer unknowingly introduced a new library, and then created a number of libraries to implement a single interface situation, which will destroy the automatic assembly, resulting in a series of users can easily ignore the invisible problem.

By introducing namespaces as much as possible, you can increase the readability of your code.

<?phpdeclare(strict_types=1);use DI\ContainerBuilder;use ExampleApp\HelloWorld;use function DI\create;require_once dirname(__DIR__) . ‘/vendor/autoload.php‘;$containerBuilder = new ContainerBuilder();$containerBuilder->useAutowiring(false);$containerBuilder->useAnnotations(false);$containerBuilder->addDefinitions([    HelloWorld::class => create(HelloWorld::class)]);$container = $containerBuilder->build();$helloWorld = $container->get(HelloWorld::class);$helloWorld->announce();

Now it seems that we are making a fuss about what we have done before.

There's no need to worry, and when we add other tools to help us guide the request, the container comes in handy. It loads the correct classes as needed at the right time.

Middleware

If you think of your application as an onion, request to enter from outside, reach the center of the onion, and turn back into a response. So middleware is every layer of onion. It receives the request and can process the request. Either pass the request to a more layer, or return a response to the outer layers (this can happen if the middleware is checking for specific conditions that the request does not meet, such as requesting a non-existent route).

If the request passes through all the layers, then the program starts processing it and transforms it into a response, and the middleware receives the response in the same order as it received the request, and can modify the response and then pass it to the next middleware.

Here are some of the highlights of some middleware use cases:

    • Debugging issues in the development environment
    • Gracefully handle exceptions in a production environment
    • Limit the frequency of incoming requests
    • Responding to requests for incoming unsupported resource types
    • Working with cross-domain resource sharing (CORS)
    • To route the request to the correct processing class

So is middleware the only way to implement these features? Of course not. But the implementation of middleware makes your understanding of the request/response life cycle clearer. This also means that you can debug more easily and develop faster.

We will benefit from the last use case listed above, which is routing.

Routing

A route relies on incoming request information to determine which class should handle it. (For example /products/purple-dress/medium , the URI should be ProductDetails::class processed by the class purple-dress and passed in medium as a parameter)

In the example application, we will use the popular Fastroute route, based on PSR-15 compatible middleware implementations.

Middleware Scheduler

In order for our application to work with the Fastroute middleware---and other middleware we install---we need a middleware scheduler.

PSR-15 is the middleware standard for defining interfaces for middleware and schedulers (called "Request Processors" in the specification), which allows a wide variety of middleware and schedulers to interact with each other. We only need to select a PSR-15-compatible scheduler so that it can work with any middleware that is compatible with PSR-15.

We first install a Relay as the scheduler.

composer require relay/relay:[email protected]

Furthermore, we use Zend Diactoros as the PSR-7 implementation according to the PSR-15 middleware standard, which implements the transitive PSR-7 HTTP message.

composer require zendframework/zend-diactoros

We use Relay to receive middleware.

// ...use DI\ContainerBuilder;use ExampleApp\HelloWorld;use Relay\Relay;use Zend\Diactoros\ServerRequestFactory;use function DI\create;// ...$container = $containerBuilder->build();$middlewareQueue = [];$requestHandler = new Relay($middlewareQueue);$requestHandler->handle(ServerRequestFactory::fromGlobals());

In line 16th, we use the ServerRequestFactory::fromGlobals() necessary information to create a new request and then pass it on to Relay. This is Request the starting point for entering our middleware stack.

Now we continue to add Fastroute and request processor middleware. (Fastroute determines whether the request is legal, whether it can be processed by the application, and then the request processor is sent Request to the appropriate handler that is already registered in the Routing configuration table)

composer require middlewares/fast-route middlewares/request-handler

Then we give Hello, world! processing class to define a route. We use /hello routes here to show routes beyond the base URI.

// ...use DI\ContainerBuilder;use ExampleApp\HelloWorld;use FastRoute\RouteCollector;use Middlewares\FastRoute;use Middlewares\RequestHandler;use Relay\Relay;use Zend\Diactoros\ServerRequestFactory;use function DI\create;use function FastRoute\simpleDispatcher;// ...$container = $containerBuilder->build();$routes = simpleDispatcher(function (RouteCollector $r) {    $r->get(‘/hello‘, HelloWorld::class);});$middlewareQueue[] = new FastRoute($routes);$middlewareQueue[] = new RequestHandler();$requestHandler = new Relay($middlewareQueue);$requestHandler->handle(ServerRequestFactory::fromGlobals());

To be able to run, you also need to modify HelloWorld it to make it a callable class, meaning that the class can be called arbitrarily like a function.

// ...class HelloWorld{    public function __invoke(): void    {        echo ‘Hello, autoloaded world!‘;        exit;    }}

(Note the addition of the Magic Method __invoke() exit; .) We can do it in 1 seconds-just don't want you to miss it.

Now open the Http://localhost:8080/hello and open the champagne!

Universal Glue

The wise reader may soon see that the container is now practically useless to us, even though we are still trapped in the fence of the configuration and construction of the DI container. The scheduler and middleware work the same way without it.

And when will it be able to do its power?

Well, what if---always do this in real-world applications--- HelloWorld classes have dependencies?

Let's explain a simple dependency and see what's going on.

// ...class HelloWorld{    private $foo;    public function __construct(string $foo)    {        $this->foo = $foo;    }    public function __invoke(): void    {        echo "Hello, {$this->foo} world!";        exit;    }}

Refresh the browser ...

wow!

Look at this ArgumentCountError .

This happens because the HelloWorld class needs to inject a string to run when it is constructed, before it can wait. This is the point where the container will help you to solve the pain.
We define the dependency in the container and pass the container RequestHandler to solve the problem.

// ...use Zend\Diactoros\ServerRequestFactory;use function DI\create;use function DI\get;use function FastRoute\simpleDispatcher;// ...$containerBuilder->addDefinitions([    HelloWorld::class => create(HelloWorld::class)        ->constructor(get(‘Foo‘)),    ‘Foo‘ => ‘bar‘]);$container = $containerBuilder->build();// ...$middlewareQueue[] = new FastRoute($routes);$middlewareQueue[] = new RequestHandler($container);$requestHandler = new Relay($middlewareQueue);$requestHandler->handle(ServerRequestFactory::fromGlobals());

Sigh! When the browser is refreshed, "Hello, Bar world!" will be reflected in your eyes!

Send a response correctly

Do you still remember the statement that I mentioned earlier in the HelloWorld class exit ?

When we build the code, it allows us to get a simple, rude response, but it's not the best choice for exporting to the browser. This brutal practice gives additional HelloWorld response to the work---should actually be responsible for other classes---it will be too complex to send the correct header information and status code, and then immediately quit the application, so HelloWorld that the subsequent middleware will not be able to run.

Remember that each middleware has the opportunity to Request modify it when it enters our application, and then modify the response (in reverse order) in response to output. In addition to Request the generic interface, PSR-7 also defines an additional HTTP message structure to assist us in the latter part of the application's run-time cycle: Response . (If you want to really understand these details, read the HTTP message and what makes the PSR-7 request and response criteria so good.) )

The modification HelloWorld returns one Response .

// ...namespace ExampleApp;use Psr\Http\Message\ResponseInterface;class HelloWorld{    private $foo;    private $response;    public function __construct(        string $foo,        ResponseInterface $response    ) {        $this->foo = $foo;        $this->response = $response;    }    public function __invoke(): ResponseInterface    {        $response = $this->response->withHeader(‘Content-Type‘, ‘text/html‘);        $response->getBody()            ->write("

The container is then modified to HelloWorld provide a new Response object.

// ...use Middlewares\RequestHandler;use Relay\Relay;use Zend\Diactoros\Response;use Zend\Diactoros\ServerRequestFactory;use function DI\create;// ...$containerBuilder->addDefinitions([ HelloWorld::class => create(HelloWorld::class) ->constructor(get(‘Foo‘), get(‘Response‘)), ‘Foo‘ => ‘bar‘, ‘Response‘ => function() { return new Response(); },]);$container = $containerBuilder->build();// ...

If you refresh the page now, you will find a blank. Our application is returning the correct object from the middleware scheduler Response , but ... Is it swollen?

It didn't do anything, that's all.

We need one more thing to wrap up: the launcher. The emitter is located between the application and the WEB server (Apache,nginx, etc.) and sends the response to the client initiating the request. It actually gets the Response object and translates it into information that the server-side API can understand.

Good news! The Zend Diactoros package that we have used to encapsulate the request also includes a transmitter that sends the PSR-7 response.

It is important to note that, for example, we are only a small test sledgehammer the use of transmitters. While they may be more complex, the real application should be configured as an automated streaming emitter to handle a large number of downloads, and the Zend blog shows how to implement it.

Modified public/index.php , used to receive from the scheduler Response , and then passed to the transmitter.

// ...use Relay\Relay;use Zend\Diactoros\Response;use Zend\Diactoros\Response\SapiEmitter;use Zend\Diactoros\ServerRequestFactory;use function DI\create;// ...$requestHandler = new Relay($middlewareQueue);$response = $requestHandler->handle(ServerRequestFactory::fromGlobals());$emitter = new SapiEmitter();return $emitter->emit($response);

Refresh your browser and resume your business! This time we used a more robust way to handle the response.

Line 15th of the above code is where the request/response cycle ends in our app, and it's where the Web server takes over.

Summarize

Now you've got the modern PHP code. With just 44 lines of code, and with the help of several widely used, fully tested and reliably interoperable components, we have completed the boot of a modern PHP program. It is compatible with PSR-4, psr-7,psr-11 and PSR-15, which means that you can use any of the other vendors you choose to implement these standards to build your own HTTP messages, DI containers, middleware, and middleware schedulers.

We have a deep understanding of the techniques and principles behind our decisions, but I would like you to understand how simple it is to steer a new program without a framework. Perhaps more importantly, I hope that you can better apply these techniques to existing projects when necessary.

You can fork and download it for free in this example on the GitHub repository.

If you are looking for a higher quality decoupling package resource, I would recommend you look at Aura, great package alliances, Symfony components, Zend Framework components, Paragon's planned focus security library, and this list of PSR-15 middleware.

If you want to use the code for this example in a production environment, you may need to separate the Routing and container definitions into their respective files for better maintenance in the future when project complexity increases. I also recommend implementing Emitterstack to better handle file downloads and other large numbers of responses.

If you have any questions, doubts or suggestions, please leave me a message.

For more modern PHP knowledge, go to the laravel/php knowledge community

Teaches you to write modern PHP code without the use of frames

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.