In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article mainly explains "how to build a PHP framework from 0". The content in the article is simple and clear, easy to learn and understand. Please follow the editor's train of thought to study and learn "how to build a PHP framework from 0".
How to build your own PHP framework
Why are we going to build our own PHP framework? Perhaps the vast majority of people will say, "there are already so many frames on the market, why build wheels?" In my opinion, "building wheels is not the goal, but absorbing knowledge in the process of building wheels is the goal."
So how can you build your own PHP framework? The general process is as follows:
Entry file-> Registration self-loading function-> Registration error (and exception) handling function-> load configuration file-> request-> routing-- > (controller data model)-> response-- -- > json-> View render data
In addition, we also need unit testing, nosql support, interface documentation support, some auxiliary scripts, etc. Finally, my framework catalog is as follows:
Frame catalogue
App [PHP application directory] ├── demo [module directory] │ ├── controllers [controller directory] │ │ └── Index.php [default controller file, output json data] │ ├── logics [logic layer Where business logic is mainly written] │ │ ├── exceptions [exception catalog] │ │ ├── gateway [gateway demonstration of a logic layer implementation] │ │ ├── tools [tool class catalog] │ │ └── UserDefinedCase.php [registration framework loading pre-routing processing use case] │ └── models [data model catalog] │ └── TestTable.php [demo model file Define an one-to-one corresponding data model] ├── config [configuration directory] │ ├── demo [module configuration directory] │ │ ├── config.php [module custom configuration] │ │ └── route.php [module custom routing] │ ├── common .php [public configuration] │ ├── database.php [database configuration] │ └── nosql.php [nosql configuration] docs [interface document directory] ├── apib [Api Blueprint] │ └── demo.apib [ Interface document sample file] ├── swagger [swagger] framework [Easy PHP Core Framework Directory] ├── exceptions [exception Directory] │ ├── CoreHttpException.php [Core http exception] ├── handles [Framework Runtime Mount handling Mechanism Class Directory] │ ├── Handle.php [handling mechanism interface] │ ├── ErrorHandle.php [error handling mechanism class] │ ├── ExceptionHandle.php [uncaught exception handling mechanism class] │ ├── ConfigHandle.php [configuration file handling mechanism class] │ ├── NosqlHandle.php [nosql handling mechanism class] │ ├── LogHandle.php [log mechanism class] │ ├── UserDefinedHandle.php [user-defined processing mechanism class] │ └── RouterHandle.php [routing mechanism class] ├── orm [object relational model] │ ├── Interpreter.php [sql parser] │ ├── DB.php [database Operation Class] │ ├── Model.php [data Model Base Class] │ └── db [Database Class Catalog] │ └── Mysql.php [mysql entity Class] ├── nosql [nosql Class Catalog] │ ├── Memcahed.php [Memcahed class file] │ ├── MongoDB.php [MongoDB class file] │ └── Redis.php [Redis class file] ├── App.php [framework class] ├── Container.php [service container] ├── Helper.php [framework helper class] ├── Load.php [self-loading class] ├── Request.php [request class] ├── Response.php [response class] ├── run.php [framework application enabling script] frontend [front-end source code and resource directory] ├── src [resource directory] │ ├── components [vue component directory] │ ├── views [vue view directory] │ ├── images [picture] │ ├──... ├── app.js [root js] ├── app. Vue [root component] ├── index.template.html [front-end entry file template] ├── store.js [vuex store file] public [common resource directory Exposed to the World wide Web] ├── dist [the resource directory after the front-end build, the directory generated by build, not ignored by the release branch] │ └──... ├── index.html [front-end entry files, files generated by build This file is not ignored by the release branch] ├── index.php [backend entry file] runtime [temporary directory] ├── logs [log directory] ├── build [php package generates phar file directory] tests [unit test directory] ├── demo [module name] │ └── DemoTest.php [test demo] ├── TestCase.php [test case] vendor [composer directory] .git-hooks [git hook directory] ├── pre -commit [git pre-commit pre-commit hook sample file] ├── commit-msg [git commit-msg sample file] .babelrc [babel configuration file] .env [environment variable file] .gitignore [git ignores file configuration ] build [php Packaging script] cli [frame cli Mode Operation script] LICENSE [lincese File] logo.png [frame logo Picture] composer.json [composer configuration File] composer.lock [ Composer lock file] package.json [front-end dependency profile] phpunit.xml [phpunit configuration file] README-CN.md [Chinese version readme file] README.md [readme file] webpack.config.js [webpack configuration file] yarn.lock [yarn lock file]
Description of the framework module:
Entry file
Define a unified entry file and provide unified access files to the outside world. The internal complexity is hidden from the outside, similar to the idea of enterprise service bus.
/ / load the framework runtime file require ('.. / framework/run.php')
[file: public/index.php]
Self-loading module
Use the spl_autoload_register function to register the self-loading function into the _ _ autoload queue, and with the namespace, you can automatically load (require) class files when using a class. After the registration is done with the loading logic, we can use use and the matching namespace to declare our dependency on a class file.
[file: framework/Load.php]
Error and exception module
While the script is running:
Error:
User custom error handling methods are registered through the function set_error_handler, but set_error_handler cannot handle the following levels of errors, E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of the E_STRICT generated in the file where the set_error_handler () function is called. So we need to use register_shutdown_function in conjunction with error_get_last to get the * errors that the script terminates, in order to customize the handling of different error levels and fatal errors, such as returning error messages with friendly prompts.
[file: framework/hanles/ErrorHandle.php]
Exception:
Uncaught exception handling methods are registered through the function set_exception_handler to catch uncaught exceptions, such as returning friendly prompts and exception information.
[file: framework/hanles/ExceptionHandle.php]
Profile module
Load framework customized and user customized configuration files.
[file: framework/hanles/ConfigHandle.php]
Input and output
Define request object: contains all request information
Define response object: declare response-related information
All the exception output and controller output in the framework are in json format, because I think it's very friendly today when the front and rear ends are completely separated, and we don't need to think about anything else right now.
[file: framework/Request.php]
[file: framework/Response.php]
Routing module
Through the url information accessed by the user, the member method of the target controller class is executed through routing rules. Here I roughly classify routes into four categories:
Traditional routing
Domain/index.php?module=Demo&contoller=Index&action=test&username=test
Pathinfo routing
Domain/demo/index/modelExample
User-defined routin
/ / defined in the config/moduleName/route.php file, the this of this point to the RouterHandle instance $this- > get ('v1 App username infooted, function (Framework\ App $app) {return 'Hello Get Router';})
Microcell routing
I would like to elaborate on the so-called micro-cell routing here. Today, when SOA-oriented and micro-service architectures are popular, many teams are moving towards service-oriented, but the complexity of many problems in the service-oriented process increases exponentially, such as distributed transactions, service deployment, cross-service problem tracking, and so on. This makes it difficult for small teams to move from a single architecture to a service architecture, so some people have proposed a micro-monomer architecture. According to my understanding, in the SOA process of a single architecture, we put each service in the micro-service into the same single unit in the form of modules, such as:
App ├── UserService [user service module] ├── ContentService [content service module] ├── OrderService [order service module] ├── CartService [shopping cart service module] ├── PayService [payment service module] ├── GoodsService [commodity service module] └── CustomService [customer service module]
As above, we simply build each service module in a single unit, but how do these modules communicate? As follows:
App::$app- > get ('demo/index/hello', [' user' = > 'TIGERB'])
Through the above way, we can loosely couple the communication and dependence of each module under the single unit. At the same time, the development of the business is difficult to predict. When we migrate to the SOA architecture in the future, it is very simple. We only need to separate the previous modules into each project, and then transform the implementation of the get method of the App instance into the strategy of RPC or REST. We can adjust the corresponding strategy through the configuration file or register our own, third-party implementation.
[file: framework/hanles/RouterHandle.php]
The traditional MVC model is advocated as MCL model.
The traditional MVC pattern includes the model-view-controller layer, most of the time we will write the business logic to the controller layer or the model layer, but slowly we will find the code difficult to read, maintain, and expand, so I force the addition of a logics layer here. As for how to write the code in the logic layer, it is entirely up to you to define, you can implement a tool class in it, or you can create a new subfolder and build your business logic code in it. You can even implement a gateway based on the responsibility connection pattern (I will provide a specific example). In this way, our final structure looks like this:
M: models, responsibilities are only related to data model-related operations
C: controllers, which is responsible for exposing resources to the outside world. Under the front-end separation architecture, controllers is actually equivalent to a view in json format.
L: logics, where responsibility flexibly implements all business logic
Logics logic layer
Example of a logical layer implementation gateway:
We have added a gateway directory under the logics layer directory, and then we have the flexibility to write logic in this directory. The structure of gateway is as follows:
Gateway [gateway logical directory under the Logics layer directory] ├── Check.php [Interface] ├── CheckAppkey.php [verify app key] ├── CheckArguments.php [verify required parameters] ├── CheckAuthority.php [verify access permissions] ├── CheckFrequent.php [verify access Frequency] ├── CheckRouter.php [gateway routing] ├── CheckSign.php [verification signature] └── Entrance.php [gateway entry file]
The gateway entry class is mainly responsible for initializing the gateway. The code is as follows:
/ / initialize one: check $checkArguments = new CheckArguments () for required parameter verification; / / initialize one: app key check $checkAppkey = new CheckAppkey (); / / initialize one: check $checkFrequent = new CheckFrequent () for access frequency verification; / / initialize one: check $checkSign = new CheckSign () for signature verification; / / initialize one for access rights verification check $checkAuthority = new CheckAuthority () / / initialize one: gateway routing rule $checkRouter = new CheckRouter (); / / constitute the object chain $checkArguments- > setNext ($checkAppkey)-> setNext ($checkFrequent)-> setNext ($checkSign)-> setNext ($checkAuthority)-> setNext ($checkRouter); / / start the gateway $checkArguments- > start (APP::$container- > getSingle ('request'))
After implementing this gateway, how can we use it in the framework? I provide an entity class of user-defined in the logic layer directory. We register the entry class of gateway with the class UserDefinedCase. The example is as follows:
/ * @ var array * / private $map = [/ / demonstrate loading custom gateway 'App\ Demo\ Logics\ Gateway\ Entrance']
So that the gateway can work. Moving on to the UserDefinedCase class, UserDefinedCase is executed before the framework is loaded into the routing mechanism, so we can flexibly implement some custom processing. This gateway is just a demonstration, you can organize your logic freely.
Where is View View? Due to the choice of complete front-and back-end separation and SPA (single-page application), the traditional view layer is also removed. See below for a detailed description.
[file: app/*]
Use Vue as the view
Source code directory
The general trend of complete front and rear separation, two-way data binding, modularization and so on. Here I ported my own open source vue front-end project structure easy-vue to this project as a view layer. We put all the front-end source files in the frontend directory. The details are as follows, or you can define them yourself:
Frontend [frontend source code and resource directory Here we store our entire front-end source file] ├── src [resource directory] │ ├── components [write our front-end components] │ ├── views [assemble our view] │ ├── images [picture] │ ├──. .├── app.js [root js] ├── app.vue [root component] ├── index.template.html [front-end entry file template] └── store.js [status management This is just a demonstration, you can write files and directories very flexibly]
Build step
Yarn install DOMAIN=http:// your domain name npm run dev
After compilation
After the success of build, the dist directory and the entry file index.html are generated in the public directory. The non-release branch .gitignore file ignores these files, and the release branch removes the ignore.
Public [public resource directory, exposed to the World wide Web] ├── dist [the resource directory after the front-end build, the directory generated by build, not ignored by the publishing branch] │ └──... ├── index.html [front-end entry files, files generated by build, not ignored by the publishing branch]
[file: frontend/*]
Database object relational mapping
What is the database object relational mapping ORM (Object Relation Map)? According to my current understanding: as the name implies is to establish the relationship between objects and abstract things, the model entity class is actually a concrete table in database modeling, and the operation on the table is actually the operation on the model instance. Perhaps the vast majority of people will ask, "Why do you want to do this? isn't it good to operate sql statements directly?" My answer: direct sql statements of course, everything is flexible, but from a project reusable, maintainable, extensible, using ORM ideas to deal with data operations is taken for granted, think if you see a number of code difficult to read and unable to reuse sql statements, what kind of mood you feel.
For the specific implementation of ORM on the market, there are thinkphp series frames, Active Record,yii series frames, Active Record,laravel series frames, Eloquent (said to be elegant), so we call it ORM here. Then modeling for ORM, the first is the ORM client entity DB: initialize different db policies through the configuration file, and encapsulate all the actions of operating the database, and finally we can operate the database directly through the DB entity. At present, I only implement mysql (responsible for establishing the connection and the underlying operation of db) of the db strategy here. Then we separate the sql parsing function of the DB entity into the trait of a reusable sql parser. The specific function is to parse the chained operation of the object into specific sql statements. *. Build our model base class, model,model, and directly inherit DB. The structure of * is as follows:
├── orm [object-relational model] │ ├── Interpreter.php [sql parser] │ ├── DB.php [database operation class] │ ├── Model.php [data model base class] │ └── db [database class directory ] │ └── Mysql.php [mysql entity class]
Example of using DB class
/ * * DB example * * findAll * * @ return void * / public function dbFindAllDemo () {$where = ['id' = > [' > =', 2],]; $instance = DB::table ('user') $res = $instance- > where ($where)-> orderBy ('id asc')-> limit (5)-> findAll (['id','create_at']); $sql = $instance- > sql; return $res;}
Example of using Model class
/ / controller code / * model example * * @ return mixed * / public function modelExample () {try {DB::beginTransaction (); $testTableModel = new TestTable (); / / find one data $testTableModel- > modelFindOneDemo (); / / find all data $testTableModel- > modelFindAllDemo (); / / save data $testTableModel- > modelSaveDemo () / / delete data $testTableModel- > modelDeleteDemo (); / / update data $testTableModel- > modelUpdateDemo (['nickname' = >' easy-php']); / / count data $testTableModel- > modelCountDemo (); DB::commit (); return 'success';} catch (Exception $e) {DB::rollBack () Return 'fail';}} / / TestTable model / * Model example * * findAll * * @ return void * / public function modelFindAllDemo () {$where = [' id' = > ['> =', 2],] $res = $this- > where ($where)-> orderBy ('id asc')-> limit (5)-> findAll (['id','create_at']); $sql = $this- > sql; return $res;}
[file: framework/orm/*]
Service container module
What is a service container?
The service container sounds floating, but in my understanding, it simply means providing a third-party entity. We inject the classes or instances needed by the business logic into this third-party entity class. When we need to obtain an instance of the class, we obtain it directly through this third-party entity class.
What is the meaning of the service container?
In terms of design patterns: in fact, regardless of the design pattern or the actual programming experience, we all emphasize "high cohesion, loose coupling". The result of our high cohesion is that the role of each entity is extremely specialized. so there are different entity classes. When organizing a logical function, there are varying degrees of dependencies between these refined entities, for which we usually do the following:
Class Demo {public function _ construct () {/ / Class demo directly depends on RelyClassName $instance = new RelyClassName ();}}
There is no logical problem with this writing, but it is not in line with the "least know principle" of the design pattern, because there is a direct dependency between them, and the whole code structure is tightly coupled. So we provide a third-party entity to transform direct dependence into dependence on a third party, and we obtain the instance of dependency directly through the third party to achieve the purpose of loose coupling. here, the role played by this third party is similar to the "middleware" in the system architecture, both coordinating dependencies and decoupling. * the third party here is the so-called service container.
After implementing a service container, I injected Request,Config and other instances into the service container as a singleton. It is very convenient to get it from the container when we need to use it. The use is as follows:
/ / inject singleton App::$container- > setSingle ('alias, easy to get', 'object / closure / class name'); / / example, inject Request instance App::$container- > setSingle ('request', function () {/ / anonymous function lazily loads return new Request ();}); / / get Request object App::$container- > getSingle (' request')
[file: framework/Container]
Nosql module
Provide support for nosql, provide global singleton objects, with the help of our service container, we can inject the required nosql instances into the service container through the configuration of the configuration file when the framework is started. Currently we support redis/memcahed/mongodb.
How to use it? As follows
/ / get redis object App::$container- > getSingle ('redis'); / / get memcahed object App::$container- > getSingle (' memcahed'); / / get mongodb object App::$container- > getSingle ('mongodb')
[file: framework/nosql/*]
Interface document generation and interface simulation module
Usually after we write an interface, the interface document is a problem. We use the Api Blueprint protocol to write the interface document and mock (available). At the same time, we use Swagger to achieve real-time access to the interface through the interface document (not implemented at present).
The tool for selecting the Api Blueprint API description protocol is snowboard. The specific instructions are as follows:
Instructions for generating interface documents
Cd docs/apib. / snowboard html-I demo.apib-o demo.html-s open the website, http://localhost:8088/
Instructions for using the interface mock
Cd docs/apib. / snowboard mock-I demo.apib open the website, http://localhost:8087/demo/index/hello
[file: docs/*]
Unit test module
Writing unit tests based on phpunit is a good habit.
How to use it?
Write the test file in the tests directory, refer to the DemoTest file in the tests/demo directory, and then run:
Vendor/bin/phpunit
Example of test assertion:
/ * demo test * / public function testDemo () {$this- > assertEquals ('Hello Easy PHP', / / execute the demo module index controller hello operation, asserting whether the result is equal to' Hello Easy PHP' App::$app- > get ('demo/index/hello'));}
Phpunit assertion document syntax reference
[file: tests/*]
Git hook configuration
Objective to standardize our project code and commit records.
Code specification: in conjunction with php_codesniffer, the coding format of the code is forcibly verified before the code is submitted.
Commit-msg specification: use ruanyifeng's commit msg specification to verify the format of commit msg, enhance the readability of git log and facilitate later error checking and statistical log, etc. Here we use Treri's commit-msg script, Thx~.
[file:. / git-hooks/*]
Auxiliary script
Cli script
Run the framework as a command line, as described in the instructions.
Build script
Package the PHP project script, and package the entire project to the runtime/build directory, for example:
Runtime/build/App.20170505085503.phar
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.