Drupal 源码分析 http://indrupal.com/ zh-hans 153. 完结篇,云客drupal源码分析系列终于完成了 http://indrupal.com/drupal/success <span>153. 完结篇,云客drupal源码分析系列终于完成了</span> <span><span>云客</span></span> <span><time datetime="2020-07-31T08:31:22+08:00" title="2020-07-31 08:31 星期五">周五, 07/31/2020 - 08:31</time> </span> <div class="text-content clearfix field field--name-body field--type-text-with-summary field--label-hidden field__item"><p><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">亲爱的《云客drupal源码分析系列》读者朋友们、Drupal社区伙伴们:</span></span></span></span></span></p> <p style="text-indent: 24pt;"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">这一天:2020年7月30日,是一个值得庆祝的日子,《云客drupal源码分析系列》终于完成了!</span></span></span></span></span>共一百一十余万字,<span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">这标志着中国缺少Drupal文档的状态被终结,核心所有必备的知识点在该系列中均得以覆盖,曾几何时,关于Drupal的技能和学习曲线网络上流传着下面这张图:</span></span></span></span></span></p> <img alt="在云客drupal源码分析前" data-entity-type="file" data-entity-uuid="c8f88514-83dd-4024-9a56-8850eee91263" src="/sites/default/files/inline-images/yunke_source_analysis_before.jpg" class="align-center" /><p align="left" style="text-align:left; text-indent:24.0pt"> </p> <p align="left" style="text-align:left; text-indent:24.0pt">不论是否那都已成过去,现在,<span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">《云客drupal源码分析系列》已完成,Drupal的学习和能力将是下面这样的:</span></span></span></span></span></p> <img alt="在《云客drupal源码分析系列》之后" data-entity-type="file" data-entity-uuid="1dada00a-3f5c-4946-a8dd-ff5e9b6d1660" src="/sites/default/files/inline-images/yunke_source_analysis_later.png" class="align-center" /><p align="left" style="text-align:left; text-indent:24.0pt"> </p> <p align="left" style="text-align:left; text-indent:24.0pt"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">该工程从Drupal8 alpha版开始到Drupal9正式版本发布,历时六年,发布字数一百一十余万字,文档通过源代码阅读的方式独立写作,辅以自己的理解总结,保证了文档的深度和广度,在深度上,不仅有宏观总结,大量内容也详细到对某一个变量、某一个方法进行详细介绍,力争不给开发者留下一个疑点;在广度上,该系列极大的补充了官网英文文档,在官网英文文档中尚没有的、解释不清晰的或错误的地方,在该文档中大量首发、清晰化或者纠正,现在很高兴的宣布该工程完工!</span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:24.0pt"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">在该系列文档中,读者朋友们会发现对Drupal以及其中的变量、方法、服务、插件等等均用“她”来称呼,注意这不是一个无意的错误,而是透露着云客对它们如女儿般的疼爱,同样的,当用到“我”这个字时,全部用“云客”代替,云客不仅是我的网络昵称,在我心中,“云客”是一个特殊的符号,代表着品质和各种精神,因此“云客”要高于“我”,时刻要求着“我”,但在系列完成的时间里,我想用“我”这个字,以随意、平常的心聊一些话题。</span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:24.0pt"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">先来说一说当初为什么选择了Drupal,其实这个话题说过很多次,详见末尾的各种参考链接,这里结合自己的履历和想法再做些补充,我在2008年写下我的第一个PHP语言“Hello World”,当时国内盛行DZ、帝国CMS、织梦、PHPCMS等,于是自己也使用它们,也为它们做额外开发,直到后来满足不了需求了,开始采用CI等框架,在这个过程中意识到了灵活性、技术债、设计均衡等概念的重要性,但这还不足以做出最后的选择,直到有一天,我的一个非技术类的朋友兴致勃勃的向我介绍他的产品,这个朋友本是做销售的,对技术并不太了解,但却自立门户开了一家技术公司,本不太在意,但展示完产品后我如大梦初醒,“你看,我们为各种行业打造了官网系统,提供了数千计的模板,只需要点几下鼠标就能拥有一个官网,客户还不用服务器,如果需要商城,没问题,初始化就行,全部自动对接,公众号?供销系统?真伪系统?HR系统?在这里在这里,点击初始化就行,全部是响应式的,我们还可以做接口对接到企业原有的各种系统”朋友一边介绍,一边演示,时不时自我感叹一下,这些从几百元起步,几千块就能提供全套服务,几万块就能完成各系统形成的信息孤岛整合;让我们站在老板们的角度想想看,什么最重要?在满足需求的情况下无疑是成本,绝对不是技术,就像要在墙上钻个孔,在符合要求的情况下,我们只关心成本,而不会去关心师傅手里的电钻有多先进,只要满足要求,成本是第一要素。现如今淘宝、京东等取代了自建商城,公众号取代了官网,这些都和成本有莫大关系。那一天我一直在思考一个问题:“这些东西都被自动化的现有系统做了,那我们开发者还做什么呢?以后的路在哪里?”,似乎只有两条:高端定制、自营项目,无论哪一条路都显示着这个时代技术门槛已提高了,决不能去选择简单的东西,否则将渐入窘境,这要求我们如果还打算走技术之路的话,必须深入,此时需要一套灵活、强大、不欠技术债、有生命力的基础系统,在对比了世界三大系统后,放弃Joomla、WordPress,选择了Drupal。</span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:24.0pt"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">漫长岁月、青灯黄卷之后,我想安静的面对《云客Drupal源码分析系列》的读者朋友们,如果你一直跟随阅读、学习,到此你应已完全掌握Drupal,能够用她进行任意开发,此刻请你感谢自己多年来的付出和坚持,恭喜你,将来的你会感谢现在的你。我不知道该系列读者的具体人数,但我期待着其中一部分能成为核心开发者,世界在巨变,中国在崛起,盼望着你们中的一部分能代表着中国接棒未来Drupal开发的重任,成为与中国大国形象匹配的推进力量。</span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:0cm"> </p> <p align="left" style="text-align:left; text-indent:0cm"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">后记关于《云客Drupal源码分析系列》的善后工作:</span></span></span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:24.0pt"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span lang="EN-US" style="font-size:12.0pt" xml:lang="EN-US" xml:lang="EN-US"><span style="font-family:宋体">Drupal9</span></span><span style="font-size:12.0pt"><span style="font-family:宋体">延续了Drupal8,主要架构几乎不变,因此该系列同样适用于Drupal9,有变化的地方我将逐步进行修正,由于该系列的写作过程也是我自己的学习过程,直到最后才理解了系统全貌,因此难免会有局限,尤其是前三分之一部分,我将一并进行修正,除此外接下来我依然会继续书写一些常用模块的介绍,不过将是以不定期的方式发布。以上的这些工作都需要时间,读者们也可以到爱码文档汇(nowicode.com)阅读更多文档,那里有很多优秀的作者发布的资料,或原创,或翻译,或视频等等,总之值得一看。</span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:0cm"> </p> <p align="left" style="text-align:left; text-indent:0cm"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">关于我(云客)的去向:</span></span></span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:24.0pt"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">关于我自己,为了专心完成源码分析的最后部分,我已从原来的公司离职,目前处于自由职业状态,在接下来的时间里,我有两个打算:</span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:0cm"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">愿望一:去做Drupal应用开发,发挥出她的价值,这么了不起的系统有太多应用领域(涵盖了移动app、小程序、物联网、网站等),本来研究她的目的就是去完成了不起的应用,如果你愿与我同行,可以联系,总之就是去解决需求、创造价值,践行作为一个人的使命。</span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:0cm"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">愿望二:此刻真有问天再借五百年的冲动,在写这个系列前还是一个三十不到的小伙子,现已快四十了,期间太过忽略家庭,这么多的时间本可以去做一些盈利的事情让家人过的好一点,但因对技术的痴迷,做了一个对他们来说自私的人,没有去积累财富,没有去提升事业,多么两难的事情,我知道留给我的时间不多了,但有一事难以放下:中国需要一本系统性介绍Drupal开发的中文书,可以是电子书,也可以是能买到的纸质版,这和源码分析有些许不同,这需要进一步整理,有漂亮规范的排版、反复推敲的顺序、系统的章节设置、良好的宏微观切换等等。源码分析系列的完成也意味着我完全掌握了Druapl,做到这一点除了努力外,更多的是有此机缘,感谢世界给了自己长达六年的条件去完成此事,这种机缘是稀少的,发现桃园者应引路,攀登者应留绳,这本书就是攀登Drupal后想留下的梯,但这除须躬身入局外,还需外界支持,因此我的第二个打算是去众筹这本书,各方面评估后大约需要至少十万费用,支持者将免费得到查阅该书电子版的账号,也接受赞助,赞助者将被列入首页,该书能否出版不取决于我一个人,任何想推进此事的个人或公司都可以联系我,群策群力,预计在九月份之前作出决定,若通过众筹或其他方式集资成功,将即刻启动该书的编制,恳请大家参与此事,一起努力!</span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:0cm"> </p> <p align="right" style="text-align:right; text-indent:0cm"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt"><span style="font-family:宋体">云客(20200730)</span></span></span></span></span></p> <p align="left" style="text-align:left"><strong>一年以后(这一段补充写于一年后2021年7月30日)</strong><br /> 太巧,今天刚好是一年后的同一天,补充一下这一年里云客做了些什么:</p> <ul><li align="left" style="text-align: left;">首先于2020年10月组织举办了深圳Drupal社区线下聚会,这是疫情下全球2020年下半年唯一的一次聚会(无一人传播感染新冠),非常成功,是深圳地区有史以来参与人数最多的一次Meeting,会议上认识了许多Drupal新伙伴,和一些投资、创业方面的朋友。</li> <li align="left" style="text-align: left;">接着在2020年12月创建了“未来很美(深圳)科技有限公司”(网址:www.will-nice.com),专门从事Drupal开发,这是深圳第一家专业Drupal开发公司。</li> <li align="left" style="text-align: left;">在2021年4月获得Drupa全球奖学金,被邀请免费参加一年一度的DrupalCon会议,在会议上结识了一些国际友人,特别是来自保加利亚的Maria Totova,她是国际组织“编程女孩”的创始人,在保加利亚大学教授Drupal课程。</li> <li align="left" style="text-align: left;">继续贡献模块和文档编写,贡献了二维码模块、支付模块等等,编写了Drupal前后端分离方面的文档教程,如json API、RESTful</li> <li align="left" style="text-align: left;">实现了一个零突破:开发完成中国首个Drupal 9 发行版系统:未来很美统一收银系统,这是来自中国的第一个Drupal9发行版系统,免费、开源且已上传到官网发行版栏目</li> </ul><p align="left" style="text-align:left">关于Drupal方面就是以上这些了,此外认识了很多来自大健康行业、投融资行业方面的朋友,很充实的一年,遗憾的是关于“Drupal开发”纸质版书籍编写尚未开始,需要等待时间和资金方面合适的机会,希望该愿望早日实现</p> <p align="left" style="text-align:left"> </p> <p align="left" style="text-align:left"> </p> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span style="font-size:12.0pt">爱码文档汇地址:http://www.nowicode.com/</span></span></span></span></p> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="line-height:18.0pt"><span style="font-family:等线"><span lang="EN-US" style="font-size:12.0pt" xml:lang="EN-US" xml:lang="EN-US">Drupal8&amp;9</span><span style="font-size:12.0pt">开发学习入门指引:http://www.indrupal.com/drupal/start </span></span></span></span></p> <p align="left" style="text-align:left"> </p> <p align="left" style="text-align:left"> </p> </div> <section data-drupal-selector="comments" class="comments"> <h2 class="comments__title">反馈互动<span class="comments__count">11</span></h2> <div class="add-comment"> <div class="add-comment__form"> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=213&amp;2=comment&amp;3=comment" token="mkICtYdpeOOTVZaBr1ncDdjSWUxkImTHkNE7N4BJUWg"></drupal-render-placeholder> </div> </div> <article data-comment-user-id="0" id="comment-11" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1619057234"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>晴空 (未验证)</span></p> <p class="comment__time">4 年 7 个月 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/11#comment-11" class="permalink" rel="bookmark" hreflang="zh-hans">George Bernard Shaw said …</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>George Bernard Shaw said “The reasonable man adapts himself to the world; the unreasonable one persists in trying to adapt the world to himself. Therefore, all progress depends on the unreasonable man.” </p> <p>萧伯纳说,识时务的人适应社会。不识时务的人坚持试着让世界适应自己。因此,所有的进步,都有赖于那不识时务的人。</p> <p>在这个信息爆炸、标题党横行、只要你稍微有点追求你就会特别焦虑——偏偏到处都还喜欢贩卖焦虑的时代里,你可以很容易的发现这样一件事实:写技术类书籍和写言情、穿越小说是完全不一样的。因为人家一天的销量能顶你三年。</p> <p>其实Drupal中文社区的每一个人都是这本书的见证者。我们看到他几乎每个周五都会发一篇。随随便便,少则两三千字,多则上万——就这样一直持续了6年。时间真是过得很快。不过时间是公平的,每个人每天都只有24个小时。有些人会选择每周发布一篇自己的文字,也会有其他一些人选择每天发两篇别人写的东西。这背后都一定有着他们自己的原因。Drupal和她的社区,能够走到今天,多半还是因为有那么一些人,真的喜欢Drupal。所以很多时候,其实原因很简单,能坚持下去仅仅只是因为“喜欢”。</p> <p>了解我的人,知道我也写过两本Drupal教程,2014年-2015年,2017年-2019年我分别写了一本关于Drupal7和Drupal8的主题教程。令人欣慰的是,看的人不少,反响也不错。很多圈内的朋友后来告诉我,他们入门Drupal是从看我的教程开始的。我的这两本书加上云客的《源码分析》,也就构成了NowICode最初的内容,后来经过社区其他同好的帮助,又集合了其他几本书的内容。形成了现在的网站。网站很小,对于初学者来说,仅仅是刚刚够用而已。</p> <p>NowICode因为是我自己在做运营,后台数据告诉我,《源码分析》的阅读量远没有我的主题教程受欢迎。很奇怪吗?其实不然,就好象你会看到满大街卖U盘和“祖传贴膜”的,但是却没有人卖光刻机。如果我们从收入的角度去衡量云客用六年时间写110万字但是却只有很少的人看,这件事儿,你会发现,他就好像是一个在20年30年前的中国研究和制造光刻机的人。英文中有个非常贴切的成语来形容他,“He is way ahead of his time”。所以,这本书的众筹之路估计会比较坎坷。</p> <p>但是,在这里,我想说的是,无论这本书是否能众筹成功,云客都是成功的。就好象当年所有的人都觉得手机应该可以待机一个月并且用来砸核桃的时候,乔布斯却不认同;就好象当年大家都不喜欢在线购物的时候,马云却不认同。人类第一次登月成功之后,肯尼迪曾经这样说:“We choose to go to the Moon in this decade and do the other things, not because they are easy, but because they are hard.” </p> <p>所以,一件事情在大多数人眼里看来,是不可能或者没意义的,但是你不认同,你坚持做完了,这就是你的成功。因为,你为人们树立了一个里程碑,让人们知道,哦,原来还有这么一件事儿。</p> <p>六年时间很长,等于一个孩子大半的童年,现在终于写完了,希望云客能有更多时间陪孩子和家人。</p> <p>晴空</p> <p>2020年7月于美国</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=11&amp;1=default&amp;2=zh-hans&amp;3=" token="2OlWXGDyX9H8WOdMffqyPIqK9-UEMUThpAalqSbX83Y"></drupal-render-placeholder> </div> </div> </article> <article data-comment-user-id="0" id="comment-12" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1599209077"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>北京_大道 (未验证)</span></p> <p class="comment__time">4 年 7 个月 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/12#comment-12" class="permalink" rel="bookmark" hreflang="zh-hans">火前留名</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>占位!<br /> 火前留名火前留名火前留名火前留名!<br /> 好文章!<br /> 作为一个使用者,我是资深的。作为一个开发者,我是0起步的。羡慕作者的技术。一起发扬光大。</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=12&amp;1=default&amp;2=zh-hans&amp;3=" token="BfoEDj_du8Kg2bZ4ZLV8OORnM6NTQnJeLP0Y2TRkM34"></drupal-render-placeholder> </div> </div> </article> <article data-comment-user-id="0" id="comment-13" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1599209083"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>xwk (未验证)</span></p> <p class="comment__time">4 年 7 个月 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/13#comment-13" class="permalink" rel="bookmark" hreflang="zh-hans">我相信每一个对技术…</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>我相信每一个对技术,对drupal有追求的开发者或多或少都看过云客的教程,去过晴空的网站,感谢你们对中国的drupal普及做出的贡献。学习drupal的程序理念会让自己达到一个前所未有的思维高度,开拓出一片更广阔的天空。毫不夸张的说每一位PHP开发者,或者说每一位开发者都非常有必要深入的学习一下Drupal,这是留给程序世界的一块瑰宝!</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=13&amp;1=default&amp;2=zh-hans&amp;3=" token="RiqtYuayOrQ0R61AyXhKTuZ90dBZJK7L-h6xUlwZU3g"></drupal-render-placeholder> </div> </div> </article> <article data-comment-user-id="0" id="comment-14" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1627619139"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>乐佬 (未验证)</span></p> <p class="comment__time">4 年 6 个月 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/14#comment-14" class="permalink" rel="bookmark" hreflang="zh-hans">老葛你是牛人,有真正的技术和自己的想法及追求,我佩服你…</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>老葛你是牛人,有真正的技术和自己的想法及追求,我佩服你!我比你晚几年接触Drupal。记得刚开始时,基本没中文资料,只能啃英文文档,虽然Drupal的文档相比其他算是不错的,但有些概念对初学者而言真的较难理解。后来看了你的《Drupal实战》和台湾人余嘉适的那本,感觉好了很多。用过你的Field Validation等模块。这么多年来,真的很欠你一句“谢谢”,谢谢你!也欢迎你来我的小站作客(<a href="https://www.onaloop.com">https://www.onaloop.com</a>),听听音乐,放松自己,开始人生新的奋斗!祝福你后面的计划一切顺利!</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=14&amp;1=default&amp;2=zh-hans&amp;3=" token="8UGDzJVaRVfd6iveR20qCbJr5G9Tz3QkGSp3_nnHiCc"></drupal-render-placeholder> </div> </div> </article> <article data-comment-user-id="0" id="comment-16" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1627619139"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>乐佬 (未验证)</span></p> <p class="comment__time">4 年 6 个月 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/16#comment-16" class="permalink" rel="bookmark" hreflang="zh-hans">窘,昨天我把本站的站长和老葛搞混了😳…</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>窘,昨天我把本站的站长和老葛搞混了😳(因为我是从老葛的网站链过来的),抱歉抱歉~~<br /> 必须给站长点赞啊,厉害👍</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=16&amp;1=default&amp;2=zh-hans&amp;3=" token="iNiZGfKTrJky0PzkwJZEJbwLReSMduFTUE_qruEPM7M"></drupal-render-placeholder> </div> </div> </article> <article data-comment-user-id="0" id="comment-19" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1627619163"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>匿名 (未验证)</span></p> <p class="comment__time">4 年 2 个月 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/19#comment-19" class="permalink" rel="bookmark" hreflang="zh-hans">您好,有drupal合作的机会,方便的话请加我微信…</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>您好,有drupal合作的机会,方便的话请加我微信:hxsznewbone</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=19&amp;1=default&amp;2=zh-hans&amp;3=" token="KtND_41ZqQfxudGm80J8n66Ma2MnnQ4pXo09865HCVs"></drupal-render-placeholder> </div> </div> </article> <article data-comment-user-id="0" id="comment-22" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1627619139"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>小金 (未验证)</span></p> <p class="comment__time">3 年 9 个月 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/22#comment-22" class="permalink" rel="bookmark" hreflang="zh-hans">哇塞</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>我感觉都是哇塞</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=22&amp;1=default&amp;2=zh-hans&amp;3=" token="oylgXjy0uUB3Jcds3lcb9-QoE_0amCKVVm-tUUMwncY"></drupal-render-placeholder> </div> </div> </article> <article data-comment-user-id="0" id="comment-26" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1631501322"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>xkx (未验证)</span></p> <p class="comment__time">3 年 6 个月 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/26#comment-26" class="permalink" rel="bookmark" hreflang="zh-hans">国内难得的干货啊,必须支持</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>即使只是支持一句话</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=26&amp;1=default&amp;2=zh-hans&amp;3=" token="QjW4D27O59pBl8ybU0DtI88tfrNKsZMdChc4MXHH6x4"></drupal-render-placeholder> </div> </div> </article> <article data-comment-user-id="0" id="comment-47" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1730713367"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>键盘侠 (未验证)</span></p> <p class="comment__time">1 年 3 个月 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/47#comment-47" class="permalink" rel="bookmark" hreflang="zh-hans">大佬,drupal微信模块wechat of china写好了吗?等了很久了</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>4月份就上传了wechat of china,什么时候可以上线试用哦</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=47&amp;1=default&amp;2=zh-hans&amp;3=" token="9aJLPgPesOEq3K1ci-b2x30-SqmXUp3t9jJMqL43-3A"></drupal-render-placeholder> </div> </div> </article> <div class="indented"> <article data-comment-user-id="1" id="comment-52" class="comment js-comment" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1730713386"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>yunke</span></p> <p class="comment__time">4 个月 3 周 之前</p> <p class="visually-hidden"><span>键盘侠 (未验证)</span> 回复 <a href="/comment/47#comment-47" class="permalink" rel="bookmark" hreflang="zh-hans">大佬,drupal微信模块wechat of china写好了吗?等了很久了</a></p> </footer> <div class="comment__content"> <h3><a href="/comment/52#comment-52" class="permalink" rel="bookmark" hreflang="zh-hans">已经上传了</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>已经上传了</p></div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=52&amp;1=default&amp;2=zh-hans&amp;3=" token="cPcUz3_fharOXf6njT_k-7IYP2tbf_k103CBqIOVVZ8"></drupal-render-placeholder> </div> </div> </article> </div> <article data-comment-user-id="0" id="comment-50" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1730713326"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>Beewolf (未验证)</span></p> <p class="comment__time">5 个月 4 周 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/50#comment-50" class="permalink" rel="bookmark" hreflang="zh-hans">无论如何都要感谢为技术出力的人</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>我刚刚从drupal8升级到11,然后部分忘记了,所以查询问题,找到了贵站。佩服之情,难以言表。<br /> 遗憾我竟然没有参加深圳的活动,如果有下次,尽力参加支持。</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=50&amp;1=default&amp;2=zh-hans&amp;3=" token="6mj8XLqtxeTdAL6V7CgE2Fwth6X6rqOPmUqB3ZzTeUE"></drupal-render-placeholder> </div> </div> </article> </section> Fri, 31 Jul 2020 00:31:22 +0000 云客 213 at http://indrupal.com http://indrupal.com/drupal/success#comments 156. Drupal移动APP、物联网开发之RESTful使用篇 http://indrupal.com/node/234 <span>156. Drupal移动APP、物联网开发之RESTful使用篇</span> <span><span>yunke</span></span> <span><time datetime="2021-07-08T12:15:54+08:00" title="2021-07-08 12:15 星期四">周四, 07/08/2021 - 12:15</time> </span> <div class="text-content clearfix field field--name-body field--type-text-with-summary field--label-hidden field__item"><p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">Drupal<span style="font-family:宋体">远不止用于网站开发,准确的描述</span>Drupal<span style="font-family:宋体">是“一个适用于</span>App<span style="font-family:宋体">开发、小程序、物联网、网站等的后端数据中心和控制中心”。在网站开发中,我们习惯于</span>HTML<span style="font-family:宋体">,然而在</span>APP<span style="font-family:宋体">开发等其他场景中,更多的是前后端分离,这就要用到</span>RESTful<span style="font-family:宋体">模块了(这里不得不提一下</span>Drupal<span style="font-family:宋体">数据架构的优越性,这是强大的</span>RESTful<span style="font-family:宋体">模块的基础),此模块实现了一种</span>web<span style="font-family:宋体">服务,它使用</span>json<span style="font-family:宋体">、</span>xml<span style="font-family:宋体">等数据格式在前后端进行通讯,这实现了前后端“强解耦”,由此后端系统变成了一个“中心”,前端可以有多种也可以有多个,和通常的网站系统相比整个事情就不一样了,这个</span>Drupal<span style="font-family:宋体">中心可与任意系统通讯,向任意种类、任意数量的系统提供服务,这使得</span>Drupal<span style="font-family:宋体">非常适合进行移动应用开发,比如一个</span>Drupal<span style="font-family:宋体">后台同时驱动安卓应用、</span>IOS<span style="font-family:宋体">应用、网站、小程序等;同时也非常适合现在流行的微服务架构设计。</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在感叹</span>Drupal<span style="font-family:宋体">强大能力的同时,对于开发者来说,</span>RESTful<span style="font-family:宋体">模块也由此显得特别重要,本文仅讲解</span>RESTful<span style="font-family:宋体">模块</span><span style="font-family:宋体">的使用,直观的列出许多示例,关于该模块的设计以及</span>REST<span style="font-family:宋体">源的开发详见官网,实际上系统默认提供了强大的功能,我们很少需要自定义开发。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">启用</span>RESTful</b><b><span style="font-family:宋体">模块:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">进入后台的扩展列表,</span>Web services<span style="font-family:宋体">一节,要启用</span>RESTful<span style="font-family:宋体">模块</span><span style="font-family:宋体">一般需要同时启用以下几个模块:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">序列化模块(</span></span><span lang="EN-US" style="background:#d9d9d9">serialization</span><span style="background:#d9d9d9"><span style="font-family:宋体">):</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这是</span>RESTful<span style="font-family:宋体">模块的依赖项,必须启用,看名字似乎只是用于数据的序列化,好似几个函数的功能,实则远没那么简单,它不止提供实体等数据的序列化,还提供标准化,提供了</span>REST<span style="font-family:宋体">的基础功能,默认提供了</span>JSON<span style="font-family:宋体">和</span>XML<span style="font-family:宋体">两种通讯格式,通过贡献模块,可以添加更多,该模块为格式添加提供了标准框架。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span lang="EN-US" style="background:#d9d9d9">REST</span><span style="background:#d9d9d9"><span style="font-family:宋体">用户接口模块(</span></span><span lang="EN-US" style="background:#d9d9d9">restui</span><span style="background:#d9d9d9"><span style="font-family:宋体">)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">该模块不是必须的,只是为了方便我们管理</span>REST<span style="font-family:宋体">,有了它,我们可以方便的在后台启禁用和配置各种</span>REST<span style="font-family:宋体">源,该模块和</span>RESTful<span style="font-family:宋体">模块的关系,类似视图</span>UI<span style="font-family:宋体">和视图模块的关系,这是一个贡献模块,推荐下载安装,如果没有安装该模块,我们需要手动修改</span>REST<span style="font-family:宋体">源配置实体,这是很不方便的。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span lang="EN-US" style="background:#d9d9d9">HTTP</span><span style="background:#d9d9d9"><span style="font-family:宋体">基本认证模块(</span></span><span lang="EN-US" style="background:#d9d9d9">basic_auth</span><span style="background:#d9d9d9"><span style="font-family:宋体">)</span></span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">该模块不是必须的,在进行</span>REST<span style="font-family:宋体">通讯时,需要进行权限控制,同样有“用户”这个概念,因此需要进行用户认证,请求</span>Drupal<span style="font-family:宋体">时需要提供认证凭据,系统默认提供了</span>cookie<span style="font-family:宋体">认证(这也是我们在</span>HTML<span style="font-family:宋体">方式下默认的认证方式),该模块另外提供了基本认证“</span>Basic Auth<span style="font-family:宋体">”,这种认证方式在</span>HTTP<span style="font-family:宋体">头中提供用户名和密码信息,实际上传递的是用户名和密码的</span>Base64<span style="font-family:宋体">编码值,即“</span>base64_encode("<span style="font-family:宋体">用户名</span>:<span style="font-family:宋体">密码</span>");<span style="font-family:宋体">”,因此注意该方式相当于是以明文方式在每次请求中传递密码,不及</span>cookie<span style="font-family:宋体">方式安全,但这两种方式都有可能在传输途中被第三方获取认证凭据,因此安全的做法是运行在</span>https<span style="font-family:宋体">之上,如果我们不需要基本认证这种方式,也可以不启用该模块;如果需要其他认证方式可以下载各种贡献模块。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">超文本应用语言模块(</span></span><span lang="EN-US" style="background:#d9d9d9">hal</span><span style="background:#d9d9d9"><span style="font-family:宋体">)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">该模块不是必须的,</span><span style="font-family:宋体">但我们通常会使用它,</span>HAL<span style="font-family:宋体">是一种基于</span>json<span style="font-family:宋体">的数据格式,提供了</span>API<span style="font-family:宋体">的自发现性,换句话说,返回的数据不只包含所请求数据本身,还包含上下页、链接关系、引用等有助于提供额外信息的元数据,这是一种标准,为跨系统通讯提供了通用规则,其规范详见:</span>stateless.co/hal_specification.html</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">配置</span>REST</b><b><span style="font-family:宋体">:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">要使用</span>REST<span style="font-family:宋体">需要先进行配置,模块启用后,默认开启了节点实体的访问,如果需要对其他类型数据启用</span>REST<span style="font-family:宋体">访问需要启用并配置,地址为:</span>/admin/config/services/rest</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">关于</span>RESTful<span style="font-family:宋体">概念网上有许多资料,这里不多讲,简而言之就是采用相同的</span>URL<span style="font-family:宋体">依托不同的</span>HTTP<span style="font-family:宋体">方法对数据进行增删改查操作,增加数据时</span>URL<span style="font-family:宋体">不带</span>ID<span style="font-family:宋体">,删除、修改、查询时带</span>ID<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">启用并配置好后,下文讲述如何进行</span>REST<span style="font-family:宋体">操作</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">权限控制:</span></b><br /> REST<span style="font-family:宋体">操作完全采用(遵循)实体自有的访问控制,当注册好用户后,需要为其分配可用的权限,否则不会成功,为表明用户身份,客户端系统在发起</span>REST<span style="font-family:宋体">通讯时需要携带用户认证凭据,见下例。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">启用并配置好后,下文以示例讲述如何进行</span>REST<span style="font-family:宋体">操作</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">示例一、</span>PHP</b><b><span style="font-family:宋体">端发起创建:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">本例以</span>PHP<span style="font-family:宋体">语言作为客户端系统,发起</span>REST<span style="font-family:宋体">创建一篇文章,代码如下:</span></span></span></p> <pre> <code class="language-php"> $entity = [ 'title' =&gt; [['value' =&gt; '云客测试']], 'body' =&gt; [['value' =&gt; '文章正文', "summary" =&gt; "摘要内容"]], 'type' =&gt; [['target_id' =&gt; 'article']], '_links' =&gt; [ 'type' =&gt; [ 'href' =&gt; 'http://www.dp9.com/rest/type/node/article', ], ], ]; $httpOption = [ 'auth' =&gt; ['yunke', '123456'], //提供基本用户认证用户名和密码 'json' =&gt; $entity, 'headers' =&gt; [ 'Content-Type' =&gt; 'application/hal+json', //重要必选,否则失败,指定请求BODY所提供数据的格式 'X-CSRF-Token' =&gt; 'sfsxy_dKXtc5WjbY4-RJEsn0LasWgm5dg8biiwvrBvc', //跨域保护,仅在cookie认证下才需要,在本列中其实是不需要的,获取方法见后 ], ]; $client = \Drupal::httpClient(); $url = 'http://www.dp9.com/node?_format=hal_json'; //地址可参考REST配置页,格式必选,用于指定响应数据的格式 $responseData = ''; try { $response = $client-&gt;post($url, $httpOption); //注意一定是POST方法 $responseData = $response-&gt;getBody()-&gt;getContents(); } catch (RequestException $e) { //HTTP状态码大于等于400 或者网络错误将报错触发这里 $msg = $e-&gt;getMessage() . "\n"; if ($e-&gt;hasResponse()) { $msg .= "请求失败,响应状态码:" . $e-&gt;getResponse()-&gt;getStatusCode(); $msg .= "响应body:" . $e-&gt;getResponse()-&gt;getBody() . "\n"; } $responseData = $msg; } header("Content-type: application/json"); print_r($responseData); </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">替换你的域名和认证信息,运行以上代码后,将创建一篇文章,可到后台查看</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">示例二、任意第三方系统端发起创建:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这里客户端系统以</span>postman<span style="font-family:宋体">为例(一个</span>HTTP<span style="font-family:宋体">调式工具,可到</span><a href="https://www.postman.com/" style="color:blue; text-decoration:underline">https://www.postman.com/</a><span style="font-family:宋体">下载),目标同上列一样去创建一篇文章。设置</span>POST<span style="font-family:宋体">请求地址为(替换成你自己的域名):</span><br /> <span style="font-family:宋体"><span style="background:white"><span style="font-family:&quot;Helvetica&quot;,sans-serif"><span style="color:#212121">http://www.dp9.com/node?_format=hal_json</span></span></span><br /> 在请求</span>body<span style="font-family:宋体">中提交以下</span>hal json<span style="font-family:宋体">格式数据:</span></span></span></p> <pre> <code class="language-json">{ "title":[ {"value":"yunke postman"} ], "body":[ {"value":"正文内容","summary":"摘要内容"} ], "type":[ {"target_id":"article"} ], "_links":{ "type":{"href":"http://www.dp9.com/rest/type/node/article"} } } </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">请求头:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">Content-Type: application/hal+json </span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">(用于指示</span>body<span style="font-family:宋体">的数据格式)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">Authorization: Basic eXVua2U6MTIzNDU2 </span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">(采用基本认证,提供凭据,</span>POSTMAN<span style="font-family:宋体">会自动计算,在认证选项卡设置为你自己的)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">认证方式如果为</span>cookie<span style="font-family:宋体">那么需要</span>cookie<span style="font-family:宋体">头:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">Cookie: SESS945389e78880c99dc7e5fd5ebc3045b9=3H34rFPmG7avBRHYV,gVsDBO7L92CQD59mil,JwSuHTaJB6U</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">(值为你自己的)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">X-CSRF-Token: sfsxy_dKXtc5WjbY4-RJEsn0LasWgm5dg8biiwvrBvc</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">(为保护系统不受跨站请求伪造,在</span>cookie<span style="font-family:宋体">认证方式下需要提供“</span>X-CSRF-Token<span style="font-family:宋体">”头,其值可以到</span>/session/token<span style="font-family:宋体">地址获取,或者在</span>REST<span style="font-family:宋体">登录返回值中获取,见后,如果认证方式是基本认证,则不需要该请求头)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">HTTP<span style="font-family:宋体">请求方法设置为</span>POST<span style="font-family:宋体">,然后点击发送即可,系统会返回创建后的实体数据</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">示例三、通过</span>REST</b><b><span style="font-family:宋体">上传图片、文件等:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这里以默认的文章内容类型为例,通过</span>REST<span style="font-family:宋体">创建一篇带有图片的文章,客户端工具采用</span>postman<span style="font-family:宋体">。一共分两步:先上传图片,然后上传文章:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">第一步:</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">首先开启“</span>File Upload<span style="font-family:宋体">”</span>REST<span style="font-family:宋体">接口(可通过</span>REST UI<span style="font-family:宋体">开启),这用于上传图片等文件,开启后</span>POST<span style="font-family:宋体">请求的目标地址为:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">http://www.dp9.com/file/upload/node/article/field_image/?_format=hal_json</span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">即:</span>/file/upload/{entity_type_id}/{bundle}/{field_name}</span></span></p> <p style="margin-left:14px; text-indent:-10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">其他实体类型类似;设置以下请求头:</span><br /> Content-Disposition: file; filename="yunke.png"</span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">Content-Type: application/octet-stream</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">以及前列中权限凭据相关请求头,请求体直接发送文件内容,然后点击发送即可完成上传,上传成功后可到后台(</span>/admin/content/files<span style="font-family:宋体">)查看,如你所见上传后的文件名由</span>Content-Disposition<span style="font-family:宋体">头指定(所以该头是必须的),上传受到字段配置的限制,如储存位置、大小、允许的格式等等,如果不满足将不会成功。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">第二步:</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">上传成功后会返回临时保存的文件实体数据,其中“</span>fid.0. value<span style="font-family:宋体">”保存着文件实体</span>ID<span style="font-family:宋体">,记住这个</span>ID<span style="font-family:宋体">,它将用于后续创建文章实体。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">接下来通过下面的方法创建带图片的文章:</span></span></span></p> <pre> <code class="language-json">{ "title":[ {"value":"带图片的文章"} ], "body":[ {"value":"正文内容","summary":"摘要内容"} ], "field_image": [ {"target_id": 1,"alt":"yunke图片"} ], "type":[ {"target_id":"article"} ], "_links":{ "type":{"href":"http://www.dp9.com/rest/type/node/article"} } } </code></pre> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="background:#fffffe"><span style="line-height:13.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在</span>Drupal<span style="font-family:宋体">中所有的文件字段都是引用字段,这里关键步骤在于设置</span>field_image<span style="font-family:宋体">字段的</span>target_id<span style="font-family:宋体">值为前一步骤上传的文件的</span>fid<span style="font-family:宋体">。</span></span></span></span></span></p> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="background:#fffffe"><span style="line-height:13.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这样我们就上传了一篇带图片的文章了,其他文件或实体类型类似。</span></span></span></span></span></p> <p align="left" style="text-align:left">&nbsp;</p> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="background:#fffffe"><span style="line-height:13.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">关于文件上传:</span></span></span></span></span></span></p> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="background:#fffffe"><span style="line-height:13.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">上传文件必须要验证,否则很危险(可能上传</span>HTML<span style="font-family:宋体">、</span>JS<span style="font-family:宋体">、</span>php<span style="font-family:宋体">等),然而验证规则必须要来源于某处,在</span>Drupal<span style="font-family:宋体">中只有实体类型的字段信息,因此上传文件仅有该方式(自定义</span>REST<span style="font-family:宋体">源的文件上传除外),关于此的讨论请见:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><a href="https://www.drupal.org/project/drupal/issues/1927648" style="color:blue; text-decoration:underline">https://www.drupal.org/project/drupal/issues/1927648</a></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><a href="https://www.drupal.org/node/2941420" style="color:blue; text-decoration:underline">https://www.drupal.org/node/2941420</a></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b>REST</b><b><span style="font-family:宋体">非创建操作:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">更新和创建操作类似,但用</span>HTTP<span style="font-family:宋体">的</span>PATCH<span style="font-family:宋体">方法,为什么不用</span>PUT<span style="font-family:宋体">方法的理由请参考:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><a href="https://groups.drupal.org/node/284948" style="color:blue; text-decoration:underline">https://groups.drupal.org/node/284948</a></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">更新时,</span>BODY<span style="font-family:宋体">中可以只发送需要更改的数据字段。查看操作用</span>GET<span style="font-family:宋体">方法,</span>body<span style="font-family:宋体">无需数据,删除用</span>DELETE<span style="font-family:宋体">方法,</span>body<span style="font-family:宋体">无需数据。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">和创建操作相比,非创建操作的请求</span>URL<span style="font-family:宋体">中必须带有数据的</span>ID<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">用户登录与注册:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">用户登录:</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">采用</span>POST<span style="font-family:宋体">方法访问:</span><a href="http://www.dp9.com/user/login?_format=json" style="color:blue; text-decoration:underline"><span lang="EN-US" style="font-size:9.0pt"><span style="background:white"><span style="font-family:&quot;Helvetica&quot;,sans-serif">http://www.dp9.com/user/login?_format=json</span></span></span></a></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">携带</span>HTTP<span style="font-family:宋体">头:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">Content-Type: application/json</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">无需其他头,请求</span>body<span style="font-family:宋体">如下:</span></span></span></p> <pre> <code class="language-json">{ "name": "用户名", "pass": "密码" } </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">系统将返回类似如下响应:</span></span></span></p> <pre> <code class="language-json">{ "current_user": { "uid": "8", "name": "yunke" }, "csrf_token": "T8LIrGmMVs2HVf2Vh_ekv8XBFKeMBClpGv8-u-bvZsA", "logout_token": "AqvLzojLWxpgwOMVSjDXt-f3Xsn2tCgZ2Q75Xh3sZQw" } </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">该响应</span>body<span style="font-family:宋体">中</span>csrf_token<span style="font-family:宋体">被用于前文例子中的</span>X-CSRF-Token<span style="font-family:宋体">头的值,在后续请求中被需要(如果是</span>cookie<span style="font-family:宋体">认证的话);</span>logout_token<span style="font-family:宋体">用于退出登录,见下;这两个值每一次登录都不会相同;在响应头中同时包含了后续登录使用的会话</span>cookie<span style="font-family:宋体">,有这些信息后面就可以执行其他登录后请求以及登出了</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">如果登录信息错误会返回类似如下内容:</span></span></span></p> <pre> <code class="language-json">{ "message": "Sorry, unrecognized username or password." } </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">此时状态码为</span>400<span style="font-family:宋体">,客户端可通过</span>HTTP<span style="font-family:宋体">状态码来判断登录是否成功</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">思考:假设我们在</span>html<span style="font-family:宋体">端为登录表单设置了验证码,通过以上方法登录还能成功吗?怎么提交验证码呢?其实那不会影响到</span>REST<span style="font-family:宋体">登录,</span>REST<span style="font-family:宋体">登录并不走登录表单的后台逻辑,也不需要验证码,没有验证码怎么防止机器人大量试探呢?不用担心,在后台有洪水控制,同一个</span>IP<span style="font-family:宋体">当多次登录失败就会被锁定一段时间。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">退出登录:</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">采用</span>POST<span style="font-family:宋体">访问以下地址:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">http://www.dp9.com/user/logout?_format=json&amp;token=AqvLzojLWxpgwOMVSjDXt-f3Xsn2tCgZ2Q75Xh3sZQw</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这里</span>token<span style="font-family:宋体">的值来自登录时返回的</span>logout_token<span style="font-family:宋体">值,见</span>REST<span style="font-family:宋体">登录。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">设置请求头</span>Content-Type: application/json<span style="font-family:宋体">,以及相关认证头,然后发起访问</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">当成功退出时,会返回</span>204<span style="font-family:宋体">响应,没有任何</span>BODY<span style="font-family:宋体">输出</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">得到用户信息:</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">启用用户实体的</span>REST<span style="font-family:宋体">源后,采用</span>GET<span style="font-family:宋体">访问:</span><span lang="EN-US" style="font-size:9.0pt"><span style="background:white"><span style="font-family:&quot;Helvetica&quot;,sans-serif"><span style="color:#212121">http://www.dp9.com/user/8?_format=json</span></span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">需要携带认证信息</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">用户注册:</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">启用用户注册</span>REST<span style="font-family:宋体">源(</span>User registration<span style="font-family:宋体">),并配置好权限允许匿名用户注册,然后</span>POST<span style="font-family:宋体">访问以下地址:</span></span></span></p> <p style="text-indent:18.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span lang="EN-US" style="font-size:9.0pt"><span style="background:white"><span style="font-family:&quot;Helvetica&quot;,sans-serif"><span style="color:#212121">http://www.dp9.com/user/register?_format=json</span></span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">提交的</span>BODY<span style="font-family:宋体">如下:</span></span></span></p> <pre> <code class="language-json">{ "name": { "value": "fooBar" }, "mail": { "value": "foo@bar.com" }, "pass": { "value": "secretSauce" } } </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">注意:注册受到“管理</span>-<span style="font-family:宋体">配置</span>-<span style="font-family:宋体">人员</span>-<span style="font-family:宋体">账户设置”的影响,如果要求邮件验证,那么以上请求会失败,会提示密码在验证后登陆时设置;当成功后会返回账户实体数据</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">补充:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">一、</span>REST<span style="font-family:宋体">模块的历史更改:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在</span>Drupal8.2<span style="font-family:宋体">版本以前,所有的</span>REST<span style="font-family:宋体">资源配置位于</span>rest.settings.yml<span style="font-family:宋体">中,此后被转变储存到配置实体中,这样做的主要原因是需要具备配置依赖,在该版本上,还对权限控制进行了更改,不再需要</span>REST<span style="font-family:宋体">专门的权限设置,而是统一采用实体访问控制</span>API<span style="font-family:宋体">,参考:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://www.drupal.org/node/2747231</span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://www.drupal.org/node/2733435</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">二、参考文档:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">官网模块文档地址:</span>https://www.drupal.org/docs/8/core/modules/rest</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">三、在本文所有示例中,你可使用系统支持的任意格式,但需要在</span>REST<span style="font-family:宋体">源配置中启用,并在“</span><span lang="EN-US" style="font-size:9.0pt"><span style="background:white"><span style="font-family:&quot;Helvetica&quot;,sans-serif"><span style="color:#212121">Content-Type</span></span></span></span><span style="font-family:宋体">”头、查询头参数等地方指定,注意不同格式数据内容略有不同,并不一一对应,比如</span>json<span style="font-family:宋体">格式和</span>hal_json<span style="font-family:宋体">格式相比,就没有“</span>_links<span style="font-family:宋体">”字段。更多</span>json<span style="font-family:宋体">示例见这里:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://www.drupal.org/docs/8/core/modules/rest/javascript-and-drupal-8-restful-web-services</span></span></p> <p>四、关于前后端分离,Drupal还提供了jsonAPI,它和REST的区别请参见本系列JSON api主题</p> </div> <section data-drupal-selector="comments" class="comments"> <h2 class="comments__title">反馈互动</h2> <div class="add-comment"> <div class="add-comment__form"> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=234&amp;2=comment&amp;3=comment" token="YxofjnyF2RT8es-4pCtuo9SNHwHmzmd9QBf68BSAdjk"></drupal-render-placeholder> </div> </div> </section> Thu, 08 Jul 2021 04:15:54 +0000 yunke 234 at http://indrupal.com http://indrupal.com/node/234#comments 155.发起HTTP请求GuzzleHttp http://indrupal.com/node/233 <span>155.发起HTTP请求GuzzleHttp</span> <span><span>yunke</span></span> <span><time datetime="2021-07-04T12:33:15+08:00" title="2021-07-04 12:33 星期日">周日, 07/04/2021 - 12:33</time> </span> <div class="text-content clearfix field field--name-body field--type-text-with-summary field--label-hidden field__item"><p><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在很久以前,</span>php<span style="font-family:宋体">程序员需要向其他服务器发起一个</span>HTTP<span style="font-family:宋体">请求时,通常使用</span>CURL<span style="font-family:宋体">扩展功能,随着时代发展,出现了更加高级的专用库</span>GuzzleHttp<span style="font-family:宋体">,它让我们可以写更少的代码,做更多的事情,在底层的传输上,不但可以用</span>CURL<span style="font-family:宋体">,还可以用</span>PHP<span style="font-family:宋体">流,或者自定义发送方式。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">官网文档:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://guzzle-cn.readthedocs.io/zh_CN/latest/#</span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://docs.guzzlephp.org/en/stable/</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">关于该库的安装和使用详见以上网址,</span>GuzzleHttp<span style="font-family:宋体">使用处理器和中间件系统去发送</span>HTTP<span style="font-family:宋体">请求,这里仅对这两重要概念做下解释:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">处理器</span>handler</b><b><span style="font-family:宋体">:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">用于处理请求的发送和响应的接收</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">中间件</span>middleware</b><b><span style="font-family:宋体">:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">增强处理器的功能,在发送请求和接收响应期间,参与一些额外处理,有点类似</span>Drupal<span style="font-family:宋体">的钩子,比如修改查询参数、添加响应头等等。举个例子:我们在开发微信支付接口的时候,腾讯公司提供的</span>SDK<span style="font-family:宋体">就是一个</span>GuzzleHttp<span style="font-family:宋体">库</span><span style="font-family:宋体">的中间件。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在</span>Drupal<span style="font-family:宋体">中有</span>4<span style="font-family:宋体">个和</span>GuzzleHttp<span style="font-family:宋体">库相关的服务,分别如下。</span><br /> <b>HTTP</b><b><span style="font-family:宋体">客户端服务:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>http_client</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">定义如下:</span></span></span></p> <pre> <code class="language-yaml"> http_client: class: GuzzleHttp\Client factory: ['@http_client_factory', 'fromOptions'] </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">用于向其他服务器发起</span>HTTP<span style="font-family:宋体">请求,由定义可见该服务内部是由工厂实例化的,使用示例如下:</span></span></span></p> <pre> <code class="language-php"> $client = \Drupal::httpClient(); $response = $client-&gt;get('http://www.baidu.com'); $body = $response-&gt;getBody(); print_r((string)$body);die; </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在这个列子中我们就将百度的页面展示出来了</span></span></span></p> <p style="text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b>HTTP</b><b><span style="font-family:宋体">客户端工厂服务:</span></b></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">定义如下:</span></span></span></p> <pre> <code class="language-yaml"> http_client_factory: class: Drupal\Core\Http\ClientFactory arguments: ['@http_handler_stack'] </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">用于通过一些选项去实例化</span>HTTP<span style="font-family:宋体">客户端对象,从该服务可以看出我们可以在全局配置文件中,使用以下配置项:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">“</span>http_client_config<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">去覆写一些默认的</span>HTTP<span style="font-family:宋体">客户端对象所需的默认选项,详见以下:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">\Drupal\Core\Http\ClientFactory::fromOptions</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b>HTTP</b><b><span style="font-family:宋体">处理器堆栈服务:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">定义如下:</span></span></span></p> <pre> <code class="language-yaml"> http_handler_stack: class: GuzzleHttp\HandlerStack public: false factory: GuzzleHttp\HandlerStack::create configurator: ['@http_handler_stack_configurator', configure] </code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">用于创建</span>HTTP<span style="font-family:宋体">处理器堆栈,这里服务定义中的“</span>configurator<span style="font-family:宋体">”项是</span>Symfony<span style="font-family:宋体">框架中新增的服务配置器,服务配置器是服务容器的一个特性,它可让你在服务实例化后用一个回调对象来对服务做一些配置,换句话说是在服务实例化后,会被首先传递到其定义的配置器中进行配置,这里是</span>http_handler_stack_configurator<span style="font-family:宋体">服务的</span>configure<span style="font-family:宋体">方法,关于服务配置器详见:</span></span></span></p> <p style="text-indent:21.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://symfony.com/doc/current/service_container/configurators.html</span></span></p> <p style="text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b>HTTP</b><b><span style="font-family:宋体">处理器堆栈配置器:</span></b></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">定义如下:</span></span></span></p> <pre> <code class="language-yaml"> http_handler_stack_configurator: class: Drupal\Core\Http\HandlerStackConfigurator public: false arguments: ['@service_container'] </code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这是个服务配置器服务,用来配置</span>HTTP<span style="font-family:宋体">处理器堆栈服务,她会收集具备如下服务标签的服务(</span>HTTP<span style="font-family:宋体">客户端中间件服务):</span></span></span></p> <p style="text-indent:21.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">“</span>http_client_middleware<span style="font-family:宋体">”</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">并将这些服务作为</span>GuzzleHttp<span style="font-family:宋体">库的</span>HTTP<span style="font-family:宋体">中间件压入</span>HTTP<span style="font-family:宋体">处理器堆栈,容器编译器如下:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">&nbsp;&nbsp;Drupal\Core\DependencyInjection\Compiler\GuzzleMiddlewarePass</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在</span>Drupal<span style="font-family:宋体">系统中默认没有定义</span>HTTP<span style="font-family:宋体">客户端中间件服务,而</span>GuzzleHttp<span style="font-family:宋体">库原生自带了</span>4<span style="font-family:宋体">个中间件(这</span>4<span style="font-family:宋体">个中间件都和对应的选项设置有关系,如果没有它们,则对应选项会失效):</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">http_errors<span style="font-family:宋体">:当状态码大于等于</span>400<span style="font-family:宋体">时,进行异常抛出处理</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">allow_redirects<span style="font-family:宋体">:重定向相关处理</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">cookies<span style="font-family:宋体">:</span>cookie<span style="font-family:宋体">处理</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">prepare_body<span style="font-family:宋体">:预处理</span>body<span style="font-family:宋体">,如根据长短和类型设置默认的请求头等</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">详见:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">\GuzzleHttp\HandlerStack::create</span></span></p> <p style="text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b>HTTP</b><b><span style="font-family:宋体">客户端常见用法示例:</span></b></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">示例一:提交</span></span><span lang="EN-US" style="background:#d9d9d9">json</span><span style="background:#d9d9d9"><span style="font-family:宋体">数据</span></span></span></span></p> <pre> <code class="language-php"> $data = [ 'a' =&gt; 'yunke', 'b' =&gt; 100, 1 =&gt; 'drupal', ]; $client = \Drupal::httpClient(); $clientURL = 'http://www.dp9.com/yunke-app/test-1'; $HTTPOptions = [ 'json' =&gt; $data, 'headers' =&gt; ['Accept' =&gt; 'application/json'], ]; $responseData = []; try { $response = $client-&gt;post($clientURL, $HTTPOptions); $responseData = $response-&gt;getBody()-&gt;getContents(); } catch (RequestException $e) { //HTTP状态码大于等于400 或者网络错误将报错触发这里 $msg = $e-&gt;getMessage() . "\n"; if ($e-&gt;hasResponse()) { $msg .= "请求失败,响应状态码:" . $e-&gt;getResponse()-&gt;getStatusCode(); $msg .= "响应body:" . $e-&gt;getResponse()-&gt;getBody() . "\n"; } echo $msg; } </code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">示例二:以表单方式提交数据</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">同上,只是选项改为如下即可:</span></span></span></p> <pre> <code class="language-php"> $HTTPOptions = [ 'form_params' =&gt; $data, ]; </code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">&nbsp;&nbsp;&nbsp; </span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">示例三:</span></span><span style="background:#d9d9d9"><span style="font-family:宋体">以</span></span><span lang="EN-US" style="background:#d9d9d9">GET</span><span style="background:#d9d9d9"><span style="font-family:宋体">方式提交查询参数:</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">同上,只是选项改为如下即可:</span></span></span></p> <pre> <code class="language-yaml"> $HTTPOptions = [ 'query' =&gt; $data, ]; </code></pre> <p style="text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">示例四:上传文件:</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">同上,只是选项改为如下即可:</span></span></span></p> <pre> <code class="language-php"> $HTTPOptions = [ 'multipart' =&gt; [ [ 'name' =&gt; 'field_name', 'contents' =&gt; 'abc', ], [ 'name' =&gt; 'file_name', 'contents' =&gt; Psr7\Utils::tryFopen('/path/to/file', 'r'), ], [ 'name' =&gt; 'other_file', 'contents' =&gt; 'hello', 'filename' =&gt; 'filename.txt', 'headers' =&gt; [ 'X-Foo' =&gt; 'this is an extra header to include', ], ], ], ]; </code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体">示例五:发送</span></span><span lang="EN-US" style="background:#d9d9d9">cookie</span><span style="background:#d9d9d9"><span style="font-family:宋体">数据</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">同上,只是选项改为如下即可:</span></span></span></p> <pre> <code class="language-php"> $cookies = ['name' =&gt; 'yunke']; $jar = \GuzzleHttp\Cookie\CookieJar::fromArray($cookies,'www.dp9.com'); $HTTPOptions = [ 'cookies' =&gt; $jar, ]; </code></pre> <p>&nbsp;</p> <p style="text-align:justify">&nbsp;</p> <p>&nbsp;</p> <p style="text-align:justify">&nbsp;</p> </div> <section data-drupal-selector="comments" class="comments"> <h2 class="comments__title">反馈互动</h2> <div class="add-comment"> <div class="add-comment__form"> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=233&amp;2=comment&amp;3=comment" token="xmq9QDnbO0STEomcLqlvVhqBnipegehLxsKP-lpF1PQ"></drupal-render-placeholder> </div> </div> </section> Sun, 04 Jul 2021 04:33:15 +0000 yunke 233 at http://indrupal.com http://indrupal.com/node/233#comments 154. 前后端解耦Drupal JSON API http://indrupal.com/node/226 <span>154. 前后端解耦Drupal JSON API</span> <span><span>yunke</span></span> <span><time datetime="2021-05-16T15:37:59+08:00" title="2021-05-16 15:37 星期日">周日, 05/16/2021 - 15:37</time> </span> <div class="text-content clearfix field field--name-body field--type-text-with-summary field--label-hidden field__item"><p style="text-indent: 0cm;"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">&nbsp;&nbsp; 当看到</span>JSON API<span style="font-family:宋体">时,脑海中是不是就想到了它是一个服务器和各种客户端定义的接口?客户端可能是浏览器、</span>app<span style="font-family:宋体">、微信小程序等等,然后它们和服务器通过</span>JSON<span style="font-family:宋体">格式来相互传输数据。那么这个接口是随意自定义的吗?实际上为了跨应用,</span>JSON API<span style="font-family:宋体">被设计成了一个通用规范,详见:</span><br /> &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; <a href=" http://jsonapi.org/">http://jsonapi.org/</a><br /> <span style="font-family:宋体">&nbsp;&nbsp; 该规范说明客户端应该如何请求获取或修改资源,以及服务器应该如何响应这些请求,它的目标是在不影响可读性、灵活性、可发现性的情况下,实现最小化请求数和传输的数据量,目前是</span>1.0<span style="font-family:宋体">版本,后续版本仍处于发展修订中,在我们平时的项目中也应该遵循该规范。</span></span></span></p> <p style="text-indent: 0cm;"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">&nbsp; 在</span>Drupal<span style="font-family:宋体">中</span>JSON API<span style="font-family:宋体">是由核心模块</span>jsonapi<span style="font-family:宋体">负责实现的,注意</span>Drupal<span style="font-family:宋体">的</span>JSON API<span style="font-family:宋体">实现仅支持实体,换句话说</span>JSON API<span style="font-family:宋体">模块完全是基于</span>Drupal<span style="font-family:宋体">的实体(包括配置实体)而实现的,这充分发挥了</span>Drupal<span style="font-family:宋体">数据结构的优越性,不支持其他自定义的数据,但</span>Drupal<span style="font-family:宋体">提供了</span>REST API<span style="font-family:宋体">支持任意数据,她们间的区别见后。</span></span></span></p> <p style="text-indent: 0cm;"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">&nbsp;&nbsp; JSON API<span style="font-family:宋体">模块遵循开箱即用的极简原则,没有太多的配置,其设计充分考虑到了</span>Drupal<span style="font-family:宋体">的权限控制,换句话说是在其权限控制之下运作的,当启用后,默认情况下如果权限具备则全部实体数据就都可读取了。</span></span></span></p> <p style="text-indent: 0cm;"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">&nbsp; 实体数据在</span>JSON API<span style="font-family:宋体">中被称为资源</span>resources<span style="font-family:宋体">,采用</span>HTTP<span style="font-family:宋体">方法</span>POST<span style="font-family:宋体">、</span>DELETE <span style="font-family:宋体">、</span>PATCH<span style="font-family:宋体">、</span>GET<span style="font-family:宋体">分别进行资源的增、删、改、查。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">获取数据:</span></b><br /> <span style="font-family:宋体">首先来看看如何通过</span>JSON API<span style="font-family:宋体">获取数据,采用</span>GET<span style="font-family:宋体">方法访问一下实体的</span>url<span style="font-family:宋体">即可:</span></span></span></p> <pre> <code>/jsonapi/{entity_type_id}/{bundle_id}[/{entity_id}]</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在默认情况下</span>JSON API<span style="font-family:宋体">模块采用“</span>/jsonapi<span style="font-family:宋体">”作为</span>URL<span style="font-family:宋体">前缀,但可以修改(详见本篇补充内容),后续地址段依次为:</span></span></span></p> <pre> <code>entity_type_id:实体类型 bundle_id :实体bundle,如果某实体类型没有bundle,那么就以实体类型作为bundle entity_id:实体ID,即为UUID,如果不存在即是访问列表数据,如果存在即访问单个数据 </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">示例如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">访问节点下的全部(其实是前五十篇,关于分页见后)文章:</span></span></span></p> <pre> <code> http://www.你的域名.com/jsonapi/node/article</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">访问节点下的某一篇文章:</span></span></span></p> <pre> <code>http://www.你的域名.com/jsonapi/node/article/a67bb2b4-d011-4bac-972a-3f3e6b9d672b</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">服务器会返回一个</span>json<span style="font-family:宋体">响应,消息体是一个</span>JSON API<span style="font-family:宋体">对象,该对象有如下预定义根键:</span></span></span></p> <pre> <code>jsonapi:指示JSON API规范版本号、元数据等 errors:如果发生异常,那么包含错误信息 links:资源的各种连接地址,如自己的、下一篇、上一篇等等 included:包含的和主资源相关的其他资源,如用户账户等 data:一个对象或数组,表示资源数据,其中type表示资源类型,格式为“实体类型--bundle”如“node—article”, id为资源id,其值是uuid,attributes的值是资源值(也就是实体属性值),relationships表示实体引用到的另外一个资源,如用户对象等 </code></pre> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">更多预定义根键可以参考:</span></span></span></span></p> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; https://jsonapi.org/format/#document-top-level</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">以上都是返回一个完整的资源,也可以返回资源的一部分,如下:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">http://www.<span style="font-family:宋体">你的域名</span>.com/jsonapi/node/article?fields[node--article]=body,uid,title,created</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这将在</span>data<span style="font-family:宋体">中仅返回指定字段内容</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">过滤器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在明白如何获取资源后,可以进一步过滤需要的资源,使用过滤器参数即可,</span>Drupal<span style="font-family:宋体">的过滤器参数非常强大,她支持条件,以及按</span>OR<span style="font-family:宋体">或</span>AND<span style="font-family:宋体">构成的条件组,这样可以多层嵌套组合多种条件,我们先从最基本的单个条件开始讲起,一个单条件按如下格式即可:</span></span></span></p> <pre> <code>http://www.dp9.com/jsonapi/node/article? &amp;filter[title-filter][condition][path]=title &amp;filter[title-filter][condition][operator]=CONTAINS &amp;filter[title-filter][condition][value]=yunke </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这里“</span>title-filter<span style="font-family:宋体">”表示指定一个过滤器标识符,“</span>value<span style="font-family:宋体">”表示值,</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">“</span>path<span style="font-family:宋体">”表示资源的路径,即实体的某字段属性,如果是引用字段或字段的子属性值,那么用点号连接各部分,如“</span>some_relationship.1.some_attribute<span style="font-family:宋体">”、“</span>field_phone.country_code<span style="font-family:宋体">”,当过滤配置属性时还可以采用“</span>*<span style="font-family:宋体">”来代替路径的某部分</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">“</span>operator<span style="font-family:宋体">”表示比较操作,支持的比较操作定义在以下位置:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">\Drupal\jsonapi\Query\EntityCondition::$allowedOperators</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">有如下这些:</span></span></span></p> <pre> <code class="language-php"> public static $allowedOperators = [ '=', '&lt;&gt;', '&gt;', '&gt;=', '&lt;', '&lt;=', 'STARTS_WITH', 'CONTAINS', 'ENDS_WITH', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'IS NULL', 'IS NOT NULL', ]; </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">注意:在</span>url<span style="font-family:宋体">中以上操作符都会被编码,比如“</span>=<span style="font-family:宋体">”会被编码为“</span>%3D<span style="font-family:宋体">”即“</span>urlencode("=")<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">以上是完整表示,</span>Drupal<span style="font-family:宋体">提供了很贴心的简写方式,比如:</span></span></span></p> <pre> <code>http://www.dp9.com/jsonapi/node/article? &amp;filter[title-filter][condition][path]=title &amp;filter[title-filter][condition][value]=yunke </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">表示查找标题等于“</span>yunke<span style="font-family:宋体">”的资源,这里操作符被省略了,省略时默认被认为是“</span>=<span style="font-family:宋体">”,可以进一步简写为:</span></span></span></p> <pre> <code>http://www.dp9.com/jsonapi/node/article? &amp;filter[title][condition][value]=yunke </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这里省略了过滤器标识符,还可以进一步采用最精简的方式:</span></span></span></p> <pre> <code>http://www.dp9.com/jsonapi/node/article? &amp;filter[title] =yunke </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">那么如何构建条件组呢?假设我们要查询一些用户,条件限制如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">名字的最后一个是以“</span>J<span style="font-family:宋体">”开始,第一个是“</span>Janis<span style="font-family:宋体">”或“</span>Joan<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">连接如下:</span></span></span></p> <pre> <code> ?filter[rock-group][group][conjunction]=OR &amp;filter[janis-filter][condition][path]=field_first_name &amp;filter[janis-filter][condition][operator]=%3D &amp;filter[janis-filter][condition][value]=Janis &amp;filter[janis-filter][condition][memberOf]=rock-group &amp;filter[joan-filter][condition][path]=field_first_name &amp;filter[joan-filter][condition][operator]=%3D &amp;filter[joan-filter][condition][value]=Joan &amp;filter[joan-filter][condition][memberOf]=rock-group &amp;filter[last-name-filter][condition][path]=field_last_name &amp;filter[last-name-filter][condition][operator]=STARTS_WITH &amp;filter[last-name-filter][condition][value]=J </code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">如你所见,我们先定义一个组,然后定义条件时用关键词“</span>memberOf<span style="font-family:宋体">”指示本条件属于哪个组,如果没有该关键词的条件默认为第一级条件,以</span>and<span style="font-family:宋体">方式连接</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">注意不要混淆过滤器和访问控制的关系,过滤器是用户可以设定的,权限控制是后台逻辑,依然要在后台检查,通常为了提高性能,我们需要用过滤器来过滤掉用户不可访问的内容</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">常见过滤器:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">通过作者的用户名来过滤</span></span></span></p> <pre> <code>filter[uid.name][value]=admin</code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">通过账户的</span>uuid<span style="font-family:宋体">来过滤,这里采用</span>id<span style="font-family:宋体">是因为</span>JSON API<span style="font-family:宋体">规范要求</span></span></span></p> <pre> <code>filter[uid.id][value]=BB09E2CD-9487-44BC-B219-3DC03D6820CD</code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">用一个操作符但有多值的情况:</span></span></span></p> <pre> <code>filter[name-filter][condition][path]=uid.name filter[name-filter][condition][operator]=IN filter[name-filter][condition][value][1]=admin filter[name-filter][condition][value][2]=john </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">补充:过滤日期值时采用</span>ISO-8601<span style="font-family:宋体">格式</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">分页:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">如果要对查询进行分页可以这样:</span></span></span></p> <pre> <code>http://www.dp9.com/jsonapi/node/article/?page[offset]=5&amp;page[limit]=2</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">实际上</span>Drupal<span style="font-family:宋体">的</span>JSON API<span style="font-family:宋体">并不提供总量查询,主要是因为由于会对全部资源做权限检查,从而严重影响性能,为防止</span>ddos<span style="font-family:宋体">攻击,每页数据量被限制为最高</span>50<span style="font-family:宋体">个,如果确实需要更高的每页量,可以使用“</span>JSON:API Page Limit module.<span style="font-family:宋体">”模块,地址为:</span></span></span></p> <pre> <code>https://www.drupal.org/project/jsonapi_page_limit</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">此外</span>page[limit]<span style="font-family:宋体">的值只是代表返回的数据中最多有这么多数据量,而不是保证返回一定有这么多,即使还有下一页的情况,这是因为后端是以该值去做数据库查询,然后在做权限检查,这可能会将不可访问的实体去除。既然不提供总量查询,每一页数据又可能不固定,那么如何知道是否还有下一页呢?就要靠返回</span>JSON API<span style="font-family:宋体">中的</span>links<span style="font-family:宋体">根键的值了,可能存在如下子健:</span></span></span></p> <pre> <code>first:第一页链接 self: 当前页链接 next: 下一页链接 prev:前一页链接 </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">如果存在</span>next<span style="font-family:宋体">子健那么说明还有数据</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">排序:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在默认情况下资源是按</span>created<span style="font-family:宋体">升序排序的,我们可以指定排序方式,完整写法如下:</span></span></span></p> <pre> <code>sort[sort-created][path]=created sort[sort-created][direction]=DESC </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">和过滤器类似,排序也可以有简写</span></span></span></p> <pre> <code>sort=created或者sort=-created,负号表示降序</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">还能进行多条件排序:</span></span></span></p> <p style="text-indent: 21pt;"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">简写:</span></span></span></p> <pre> <code>sort=-created,uid.name</code></pre> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">完整写法:</span></span></span></p> <pre> <code>sort[sort-created][path]=created sort[sort-created][direction]=DESC sort[sort-author][path]=uid.name </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">此时按传递顺序确定先后权重</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">版本:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">要获取某个资源的某个版本可以这样:</span></span></span></p> <pre> <code>/jsonapi/node/article/ef64bc9a-a80f-4d71-b6c6-095e4aced7a2?resourceVersion=id:6</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这里</span>id<span style="font-family:宋体">后面是版本号,还可以用如下方式:</span></span></span></p> <pre> <code>/jsonapi/node/page/{{uuid}}?resourceVersion=rel:latest-version</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这里</span>rel<span style="font-family:宋体">表示关系,</span>latest-version<span style="font-family:宋体">表示最新版本,</span>working-copy<span style="font-family:宋体">表示工作副本,目前</span>JSON API<span style="font-family:宋体">模块还不支持获取版本集,版本功能也不是规范的一部分</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">目前资源的某个“版本”只能读取,暂只支持节点和媒体实体类型,当前</span>drupal<span style="font-family:宋体">还没有针对版本的访问控制机制</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">翻译:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">JSON API<span style="font-family:宋体">模块支持很简单的多语言功能,暂不支持高级应用,默认通过</span>drupal<span style="font-family:宋体">的语言协商机制实现,将来打算采用</span>JSON API<span style="font-family:宋体">规范</span><span style="font-family:宋体">的多语言机制,有一些注意事项:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">当前不支持删除某个翻译,只能完整删除</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">有限的</span>POST<span style="font-family:宋体">支持,即能够以非默认语言创建一个实体,但不允许在其上创建其他语言翻译</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">包含关联资源:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">默认情况下</span>JSON API<span style="font-family:宋体">是不包含关联资源的详细信息的,比如用户,那么可以通过关键词“</span>include<span style="font-family:宋体">”带出,比如要带出用户账户数据可以这样:</span></span></span></p> <pre> <code>http://www.dp9.com/jsonapi/node/article/a67bb2b4-d011-4bac-972a-3f3e6b9d672b?include=uid</code></pre> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">新建操作:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">JSON API<span style="font-family:宋体">模块</span><span style="font-family:宋体">的实体新建操作是通过</span>POST<span style="font-family:宋体">请求完成的,为了你方便测试,建议下载“</span>Postman<span style="font-family:宋体">”做客户端,它可以自定义</span>POST<span style="font-family:宋体">请求的各种参数。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在默认情况下,</span>JSON API<span style="font-family:宋体">模块只允许只读操作,因此需要先在以下配置页中打开写、改、删操作:</span></span></span></p> <pre> <code>“/admin/config/services/jsonapi” </code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">写操作需要具备权限,因此建立一个有写权限的账户,</span>JSON API<span style="font-family:宋体">模块的账户认证不采用</span>Drupal<span style="font-family:宋体">的标准</span>cookie<span style="font-family:宋体">会话认证机制,而是采用</span>HTTP<span style="font-family:宋体">基本认证,因此需要开启</span>WEB<span style="font-family:宋体">服务中的</span>basic_auth<span style="font-family:宋体">模块</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">一切准备就绪后就可以提交</span>POST<span style="font-family:宋体">请求了,在请求头中要有以下头:</span></span></span></p> <pre> <code>Accept: application/vnd.api+json Content-Type:application/vnd.api+json Authorization:Basic YXBpOmFwaQ== </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">其中以上的“</span>YXBpOmFwaQ==<span style="font-family:宋体">”来源于:</span></span></span></p> <pre> <code>base64_encode("用户名:密码");</code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这里是:</span>base64_encode("api:api");</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">请求体必须是</span>json<span style="font-family:宋体">格式文本,类似如下:</span></span></span></p> <pre> <code class="language-json"> { "data": { "type": "node--article", "attributes": { "title": "yunke 文章 title", "body": { "value": "云客通过json api 传送的内容", "format": "plain_text" } } } } </code></pre> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="background:#fffffe"><span style="line-height:13.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体"><span style="color:black">提交地址为:</span></span><span lang="EN-US" style="color:black">http://www.</span><span style="font-family:宋体"><span style="color:black">你的域名</span></span><span lang="EN-US" style="color:black">.com/jsonapi/node/article/</span></span></span></span></span></p> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="background:#fffffe"><span style="line-height:13.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体"><span style="color:black">注意</span></span><span lang="EN-US" style="color:black">HTTP</span><span style="font-family:宋体"><span style="color:black">方法选择</span></span><span lang="EN-US" style="color:black">POST</span><span style="font-family:宋体"><span style="color:black">,提交后,如果成功,服务器会返回新建文章的</span></span><span lang="EN-US" style="color:black">JSON API</span><span style="font-family:宋体"><span style="color:black">对象,此时就可以在后台看到新建的内容了,如果异常,将返回带错误提示的</span></span><span lang="EN-US" style="color:black">JSON API</span><span style="font-family:宋体"><span style="color:black">对象</span></span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">JSON API<span style="font-family:宋体">规范中,每个</span>POST<span style="font-family:宋体">请求仅允许创建一个资源,如果需要同时建立关联实体,可以考虑安装以下模块:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://www.drupal.org/project/subrequests</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">更新操作:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">和</span>POST<span style="font-family:宋体">新增请求几乎一样,不一样的是采用</span>PATCH<span style="font-family:宋体">请求</span><span style="font-family:宋体">方法,数据体和请求</span>URL<span style="font-family:宋体">均需要带上</span>uuid<span style="font-family:宋体">,如:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">必要的请求头:</span></span></span></p> <pre> <code>Accept: application/vnd.api+json Content-Type:application/vnd.api+json Authorization:Basic YXBpOmFwaQ== </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">body<span style="font-family:宋体">体内容:</span></span></span></p> <pre> <code>{ "data": { "type": "node--article", "id": "0c0d992c-7ae7-4f79-a87c-596f17fa2f19", "attributes": { "title": "云客20210330更新修改测试", "body": { "value": "云客通过json api 传送的内容", "format": "plain_text" } } } } </code></pre> <p align="left" style="text-align:left"><span style="font-size:10.5pt"><span style="background:#fffffe"><span style="line-height:13.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体"><span style="color:black">采用</span></span><span lang="EN-US" style="color:black">PATCH</span><span style="font-family:宋体"><span style="color:black">请求:</span></span></span></span></span></span></p> <pre> <code>http://www.你的域名.com/jsonapi/node/article/0c0d992c-7ae7-4f79-a87c-596f17fa2f19</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">服务器返回</span>200<span style="font-family:宋体">状态码,</span>body<span style="font-family:宋体">是修改后的</span>JSON API<span style="font-family:宋体">对象</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">删除操作:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">和</span>POST<span style="font-family:宋体">新建操作一样,需要一些必须的请求头和用户认证方法:</span></span></span></p> <pre> <code>Authorization:Basic YXBpOmFwaQ== </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">然后采用</span>DELETE<span style="font-family:宋体">方法访问接口即可:</span></span></span></p> <pre> <code>http://example.com/jsonapi/node/article/{{article_uuid}}</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">服务器返回</span>204<span style="font-family:宋体">响应状态码,没有消息体</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">文件上传:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">现在</span>JSON API<span style="font-family:宋体">已经支持文件上传了,详见:</span></span></span></p> <pre> <code>https://www.drupal.org/node/3024331</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">请同时参考《云客</span>drupal<span style="font-family:宋体">源码分析》的文件上传相关内容</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b>JSON API</b><b><span style="font-family:宋体">和</span><a name="_Hlk68013841"></a>RESTfull</b><b><span style="font-family:宋体">的区别:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">JSON API<span style="font-family:宋体">:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">是完全基于实体的,因此仅限于实体内容(包括配置实体),专注于</span>Drupal<span style="font-family:宋体">的优势</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">RESTfull<span style="font-family:宋体">:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">可用于任意数据,任意数据格式、逻辑、</span>http<span style="font-family:宋体">方法,可配置性很强,但复杂度高,本身不支持排序、分页等,这些由“源”决定</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">注意:在</span>RESTful<span style="font-family:宋体">中是用</span>PUT<span style="font-family:宋体">方法进行更新,而不是</span>PATCH<span style="font-family:宋体">,(但</span>Drupal<span style="font-family:宋体">的</span>RESTful<span style="font-family:宋体">模块并不是,依然是</span>PATCH<span style="font-family:宋体">),它们主要有以下区别:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">PUT<span style="font-family:宋体">是整体更新,且是幂等的(幂等</span>idempotent<span style="font-family:宋体">:一个操作执行任意次对系统的影响跟一次是相同)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">PATCH<span style="font-family:宋体">是对</span>PUT<span style="font-family:宋体">的补充,意为局部更新,不用把完整信息对象传过去,且不是幂等的</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">幂等性在网络故障的情况下非常重要,发出请求但没有收到回复时,是重新发送还是先判断是否建立再发送从而避免信息重复呢?此时必须回答幂等性问题</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b>JSON API</b><b><span style="font-family:宋体">不能做什么:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">仅处理实体的</span>CURD<span style="font-family:宋体">操作,不处理业务逻辑,比如用户登录、修改密码、</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">补充:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">1<span style="font-family:宋体">、官网文档:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">2<span style="font-family:宋体">、</span>JSON API<span style="font-family:宋体">规范:</span>https://jsonapi.org</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">3<span style="font-family:宋体">、对</span>JSON API<span style="font-family:宋体">的加强模块:</span>https://www.drupal.org/project/jsonapi_extras<span style="font-family:宋体">,其提供前缀修改、类型别名、资源禁用等等</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">4<span style="font-family:宋体">、配置实体通过</span>JSON API<span style="font-family:宋体">只能读取</span></span></span></p> </div> <section data-drupal-selector="comments" class="comments"> <h2 class="comments__title">反馈互动</h2> <div class="add-comment"> <div class="add-comment__form"> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=226&amp;2=comment&amp;3=comment" token="6C7ozEZKYgLTsdbAg1O0c_tnZgwG3mekhRWL4oAYL0M"></drupal-render-placeholder> </div> </div> </section> Sun, 16 May 2021 07:37:59 +0000 yunke 226 at http://indrupal.com http://indrupal.com/node/226#comments 152. Drupal系统初始安装逻辑 http://indrupal.com/node/212 <span>152. Drupal系统初始安装逻辑</span> <span><span>云客</span></span> <span><time datetime="2020-07-31T08:16:11+08:00" title="2020-07-31 08:16 星期五">周五, 07/31/2020 - 08:16</time> </span> <div class="text-content clearfix field field--name-body field--type-text-with-summary field--label-hidden field__item"><p><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">本篇讲解</span>Drupal<span style="font-family:宋体">系统安装原理(即系统初始安装,非模块安装),以及如何制作发行版,在阅读本篇前建议你先阅读本系列的以下已发布内容(但不是必须的):</span></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">&nbsp;&nbsp;《配置同步(导入、导出)》</span></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">&nbsp;&nbsp;《模块安装与卸载过程》</span></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">&nbsp;&nbsp;《接口翻译导入导出与删除》</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">概述:</span></b></span></span><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk37855792"><br /> <span style="font-family:宋体">如果你是一位有多年经验的开发老手,那么或多或少会接触到一些</span>CMS</a><span style="font-family:宋体">系统,通常它们的安装程序是一套独立的程序,数据库的安装则是提前准备好的</span>SQL<span style="font-family:宋体">文件,而</span>Drupal<span style="font-family:宋体">则完全不一样,所以请暂时忘记那些</span>CMS<span style="font-family:宋体">的安装方式,以便准备好理解新事物,</span>Drupal<span style="font-family:宋体">的安装方式更现代、更高级,是未来的发展方向。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">安装配置扩展(</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">installation</span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black"> profiles</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">):</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">系统的安装完全由安装配置扩展控制,简称“</span>profile<span style="font-family:宋体">”,该扩展和模块、主题是</span>Drupal<span style="font-family:宋体">世界最主要的三大扩展类型(此外还有模板引擎扩展),</span>profile<span style="font-family:宋体">几乎和模块一样,实际上在安装后系统将</span>profiles<span style="font-family:宋体">当做模块来对待,她具备模块拥有的所有性质和能力,安装后她像被启用的模块一样参与系统运行,如提供各类钩子、菜单、路由等,和模块不同的是</span>profile<span style="font-family:宋体">具备和安装相关的额外功能,用来控制系统如何安装,在安装后提供配置覆写等</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk37834757"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">Drupal</span></span></a><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">发行版(</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">Drupal distribution</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">):</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">也称为分发版,即建立在</span>Drupal<span style="font-family:宋体">上针对某种用途或领域制作的系统安装版本,安装后开箱即用,比如一个企业官网、一个电子商城系统等,其打包好了所需的全部功能,成为独立发行的</span>Drupal<span style="font-family:宋体">版本;制作发行版是通过</span>profile<span style="font-family:宋体">(安装配置扩展)来实现的,下载的发行版安装包中一定包含了实现该发行版的</span>profile<span style="font-family:宋体">扩展,但反过来,存在</span>profile<span style="font-family:宋体">却不一定就是在建立发行版,一个</span>profile<span style="font-family:宋体">扩展只代表了一种安装方式而已,比如官方下载的安装包中就存在三个默认的</span>profile<span style="font-family:宋体">扩展:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">standard<span style="font-family:宋体">(标准安装)、</span>minimal<span style="font-family:宋体">(最小化安装)、</span>demo_umami<span style="font-family:宋体">(建立一个演示站点)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在安装时用户选择一个</span>profile<span style="font-family:宋体">安装即可,但如果通过</span>profile<span style="font-family:宋体">将安装包声明成了发行版(声明方式见下文),那么会跳过选择直接安装,前文已讲到安装完全由</span>profile<span style="font-family:宋体">扩展控制,比如安装过程要执行的工作、安装界面等,因此发行版有高度自定义性,可以做到在浏览器界面上完全看不出是由</span>drupal<span style="font-family:宋体">制作的</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">安装方式:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">从被安装系统的初始状态来说,有两种安装方式:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">一、选择某个</span>profile<span style="font-family:宋体">安装一个全新的站点</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">二、将现有站点的配置导出,通过配置安装一个和现有站点一样的实例</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">从程序角度来说,也有两种安装方式:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">一、交互式安装,通过浏览器安装就属于该方式,安装程序会一步一步的和用户交互,在多个请求中逐步完成系统安装,本篇着重以该方式进行讲解</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">二、非交互式安装,通过命令行或程序调用方式的安装就属于此,这种方式下,需要提前将安装所用的参数一次性完整的传递给安装程序</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">安装原理:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">传统上我们熟悉的安装过程是将安装所需任务线性排开,从前向后执行,</span>Drupal<span style="font-family:宋体">的安装要更抽象一些,抽象往往意味着更灵活更强大,可以将</span>Drupal<span style="font-family:宋体">的安装系统看做是一个状态系统,在安装期间的每一个请求中,她会首先侦测当前处于什么状态,再以此状态决定要做什么事情,比如侦测到系统缺乏安装语言信息,就会首先给出安装语言选择表单,如果侦测到没有配置文件,那么就给出数据库配置表单,可以说所有工作均是围绕当前时间点的状态展开的,因此系统以变量</span>$install_state<span style="font-family:宋体">来保存当前的状态信息,该变量贯穿整个安装系统,非常重要。安装无非就是完成一件一件的工作,每一件工作称为一个任务,在</span>Drupal<span style="font-family:宋体">安装系统中,任务有三种类型:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i><span style="font-family:宋体">每个安装请求都会运行的任务</span></i><span style="font-family:宋体">:如准备运行环境;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i><span style="font-family:宋体">依据当前状态断定不运行的任务</span></i><span style="font-family:宋体">:如呈现数据库连接信息收集表单,在已经有配置文件时,就无须执行;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i><span style="font-family:宋体">运行过就不再运行的任务</span></i><span style="font-family:宋体">:如安装主题,已安装自然不用再安装,这是最多任务的类型;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">同一个任务在不同系统状态下,可能类型不同,任务到底是哪一种类型,保存在任务的运行旗标中(见下文)。在每一个安装请求中,系统都会初始化一个任务列表,系统以当前状态判断在本请求中执行或不执行哪些任务,从而一步步走完安装流程;</span>profile<span style="font-family:宋体">也正是通过控制这个初始化的任务列表来控制系统安装的。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">程序上,在每个安装请求中,确定当前状态后安装系统都会启动一个核心,核心初始化的容器包含了核心提供的所有服务,这样在核心启动后,在任意地方都可以通过</span>\Drupal::service()<span style="font-family:宋体">方法获取到特定功能,从而能调用核心各种现存代码去完成任务,在初期的那些安装请求中,由于还不存在数据库,系统依据状态信息知道这一点后,会将依赖于数据库的那些服务替换为内存存储或做特殊处理;在此我们需要明白:核心的启动不依赖于数据库,也不依赖于任何模块,包括核心模块,安装初期的核心是零模块的,没有模块参与不影响核心启动。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">启动安装:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在将安装包放入服务器根目录后,开始安装可以直接访问站点首页,在系统开始执行后,发现没有配置数据库,那么会重定向到安装脚本,这在以下代码中执行:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\DrupalKernel::handle</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装脚本路径为(也可以直接访问该脚本启动安装):</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">/core/install.php</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">前文已经讲到安装有两种方式:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">交互式:</span></span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">通常就是浏览器,通过多个页面请求和用户交互式的一步一步完成</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">非交互式:</span></span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">或称为一次性安装,这通常是命令行安装,或其他系统调用安装,一次性提供安装所需信息,安装在一个请求中完成,没有交互输出</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">不管是交互式还是一次性安装,均从一个入口函数完成:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">install_drupal($class_loader, $settings = [], callable $callback = NULL)</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数含义如下:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">$class_loader<span style="font-family:宋体">:类加载器</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">$settings<span style="font-family:宋体">:用于一次性安装时传递所需安装信息,详见</span>install_state_defaults()<span style="font-family:宋体">函数,交互式安装时不传递</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">$callback<span style="font-family:宋体">:一个回调,用于在一次性安装时更新进度</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在交互式安装方式下,当每一个请求到来时,总是先从当前安装状态确定安装到了哪一个步骤(或称任务</span>task<span style="font-family:宋体">),然后将控制权传给任务回调。</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">通常前端显示的安装步骤(安装任务</span>task<span style="font-family:宋体">)如下(有许多任务是后端静默执行的,并不包含在这里):</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <pre> <code> Choose language (active):选择语言 Choose profile:选择安装配置扩展 Verify requirements:需求检查 Set up database:填数据库信息 Install site:执行安装 Configure site:配置站点 </code></pre> <p style="text-align:justify; text-indent:21pt">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">各步骤对应的</span>URL<span style="font-family:宋体">如下:</span></span></span></p> <pre> <code>选择语言:/core/install.php 选择安装:/core/install.php?rewrite=ok&amp;langcode=en 需求检查:/core/install.php?rewrite=ok&amp;langcode=en&amp;profile=standard 填数据库:/core/install.php?rewrite=ok&amp;langcode=en&amp;profile=standard&amp;continue=1 执行安装:/core/install.php?rewrite=ok&amp;langcode=en&amp;profile=standard&amp;continue=1&amp;id=1&amp;op=start 配置站点:/core/install.php?rewrite=ok&amp;langcode=en&amp;profile=standard&amp;continue=1 </code></pre> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">系统安装状态数组:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">前文已讲到整个安装过程是围绕当前系统的状态信息进行的,状态信息被储存在一个数组中,该数组是一个全局变量,变量名为</span>$install_state<span style="font-family:宋体">,初始化值来自以下函数:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">install_state_defaults()</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装状态数组各键含义如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['site_path']</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">储存站点路径,一个相对于根目录的相对路径,通常为“</span>sites/default<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['active_task']</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当前正在执行的任务,默认为</span>NULL</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['completed_task']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装期间,在前一个安装请求中完成的最后一个任务的任务名,默认为</span>NULL<span style="font-family:宋体">,这被储存在状态系统中,代码获取方式:</span>\Drupal::state()-&gt;get('install_task');</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['config']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">一个数组值,储存安装期间所用到的部分配置,键名为配置对象名,键值为经过解码的配置文件内容</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['config_install_path']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当从配置安装时,配置目录的路径,详见</span>install_load_profile<span style="font-family:宋体">函数,默认为</span>NULL</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['config_verified']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">布尔值,指示配置同步目录是否已经存在</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['database_verified']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">布尔值,指示是否已经具备有效的数据库连接,有效是指能通过该连接在其中储存数据,如果为</span>true<span style="font-family:宋体">意味着站点配置文件已建立,且数据库信息已经被收集了</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['settings_verified']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">布尔值,指示是否已经建立了有效的站点配置文件,即“</span>settings.php<span style="font-family:宋体">”,有效是指不但文件存在,且里面有设置数据库连接信息和哈希盐值</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['base_system_verified']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">布尔值,默认为</span>false<span style="font-family:宋体">,指示基础系统是否已经安装,即系统模块是否已经安装,依靠判断会话数据表的存在性来确定</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['download_translation']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">布尔值,默认为</span>false<span style="font-family:宋体">,指示是否需要从翻译服务器下载翻译文件,当非英语语言被指定,又不存在对应的翻译文件时,就需要下载,实际上系统并未使用到该项,而是直接进行了判断</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['forms']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在非交互式安装中,用来传递表单提交的值,是一个数组,键名为表单</span>id<span style="font-family:宋体">,键值为要提交给表单的值,这个值会直接调用</span>$form_state-&gt;setValues($install_state['forms'][$install_form_id]);<span style="font-family:宋体">传递给系统,然后进行表单的程序方式提交,如果验证出现错误,安装将抛出异常</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['installation_finished']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">布尔值,默认为</span>FALSE<span style="font-family:宋体">,指示安装是否完成,</span>TRUE<span style="font-family:宋体">意味着所有任务执行完成,系统被完全安装</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['interactive']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">布尔值,默认为</span>true<span style="font-family:宋体">,指示安装是否处于交互模式,即安装是通过浏览器进行的(多请求交互执行)</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['parameters']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">储存</span>URL<span style="font-family:宋体">中的查询参数,来自</span>$request-&gt;query-&gt;all();</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['parameters_changed']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">布尔值,默认为</span>false<span style="font-family:宋体">,指示</span>url<span style="font-family:宋体">参数是否已经改变,当有改变时,在交互式安装情况下,当前请求需要停止,安装进程需要重定向跳转到新的请求上</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['profile_info']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装配置扩展的</span>info<span style="font-family:宋体">文件信息,已经经过默认合并处理,详见:</span>install_profile_info<span style="font-family:宋体">函数</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['profiles']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">数组,储存全部安装配置扩展的扩展对象,键名为扩展名,键值为:</span>\Drupal\Core\Extension\Extension </span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['theme']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">可选存在,字符串值,安装期间所采用主题扩展的机器名,默认为</span>seven</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['server_pattern']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">值为用于下载翻译文件的服务器</span>URL<span style="font-family:宋体">,可以自定义,默认为官方翻译服务器:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">“</span>http://ftp.drupal.org/files/translations/%core/%project/%project-%version.%language.po<span style="font-family:宋体">”</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">里面以百分号开始的为占位符,系统会用</span>strtr<span style="font-family:宋体">函数替换成相应的值:</span></span></span></p> <pre> <code class="language-php"> '%project' =&gt; 'drupal', '%version' =&gt; $version,//drupal版本号 '%core' =&gt; 'all', '%language' =&gt; $langcode, //语言代码 </code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk46578548"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state</span></span></a><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">['stop_page_request']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">布尔值,默认为</span>false<span style="font-family:宋体">,任务可以设置该项为</span>true<span style="font-family:宋体">去强制终止当前请求(即使没有返回值),使用该项是很罕见的,比如去打印一个</span>json<span style="font-family:宋体">输出,而不是主题化输出,批处理期间及是如此,但系统在批处理时会自动设置该项,不必人为设置,其他情况如需终止则需要自行设置</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['task_not_complete']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">布尔值,默认为</span>false<span style="font-family:宋体">,如果任务将该项设置为</span>TRUE<span style="font-family:宋体">,则表示任务没有完成,需要再次执行,通常批处理和表单提交会用到,但这两个情况系统会自动设置该项,任务本身不需要处理她,其他情况则需要任务自行设置</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['tasks_performed']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">储存当前请求中已经被执行的安装任务,值为由任务名构成的一个索引数组</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$install_state['translations']</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">一个数组值,默认为空数组,储存系统翻译目录(默认为</span>sites\default\files\translations<span style="font-family:宋体">)中已存翻译文件的</span>uri<span style="font-family:宋体">,键名为语言代码,键值为</span>uri<span style="font-family:宋体">,默认追加了英语作为第一个数组元素,但英语的</span>uri<span style="font-family:宋体">为空,该选项的赋值详见选择语言任务,即函数</span>install_select_language(&amp;$install_state)<span style="font-family:宋体">,选择语言任务是首个将执行的任务,且每次请求都会运行,因此该项总会被初始化</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">安装相关重要函数说明:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">install_begin_request($class_loader, &amp;$install_state)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在安装过程中的每一个请求开始执行时,均会运行该函数,她的作用是侦测系统当前的状态,从而确定状态数组的值,并为后续执行准备运行环境,即启动核心并创建容器。在数据库可用前,涉及到数据库存储的服务(如缓存、状态等),会被替换成采用内存存储或空储存,替换的服务详见以下服务提供器:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">Drupal\Core\Installer\InstallerServiceProvider</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">此外,该函数还进行以下工作:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在数据库可用前,会给翻译服务添加文件型翻译器,以便从翻译目录读取翻译数据并显示;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果能够唯一确定安装配置扩展、语言则确定下来,这样后续将不会让用户选择;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">将系统模块和安装配置扩展注入到模块处理器中(不论是否已安装),并加载她们的主扩展文件;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">确定安装使用的主题扩展;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">确定上一个请求最后完成的任务;</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">install_run_tasks(&amp;$install_state, callable $callback = NULL)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">循环执行任务,直到有以下条件满足时才停止:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、全部任务执行完成</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、在交互式安装中,任务有需要发送给浏览器的输出信息,即任务回调返回了渲染数组</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、在交互式安装中,</span>URL<span style="font-family:宋体">参数发生改变,这表示需要重定向</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、在交互式安装中,</span>$install_state['stop_page_request']<span style="font-family:宋体">被设置为真,比如批处理请求</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk46595424"></a><a name="_Hlk46301959"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">install_tasks</span></span></a><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">($install_state)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">初始化任务列表,关于任务定义详见本文任务定义一节,全部安装任务来自核心以及被选中的配置扩展</span>profile<span style="font-family:宋体">,该函数返回所有的任务,而不管其是否已经执行,但在任务的运行旗标中给出了执行方式的信息(详见任务定义中的</span>run<span style="font-family:宋体">选项</span>]<span style="font-family:宋体">),返回数组,键名为任务机器名,键值为对应的任务定义数组;注意返回的安装任务会基于</span>$install_state<span style="font-family:宋体">信息而变化</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">install_tasks_to_perform($install_state)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">从</span>install_tasks<span style="font-family:宋体">函数取得系统中所有的任务,过滤并返回当前需要真正执行的任务列表,如果任务满足以下三种条件之一,那么将被去除:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、运行旗标</span>$task['run']<span style="font-family:宋体">为</span>INSTALL_TASK_SKIP<span style="font-family:宋体">的任务被去除</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、在本次请求中已执行的任务被去除</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、如果在上一个请求结束时设置了最后被运行的任务(其任务名储存在</span>\Drupal::state()-&gt;get('install_task');<span style="font-family:宋体">中),那么她和她之前的任务均会被去除(这意味着在整个安装流程中已执行过的任务不会再重复执行),但任务旗标</span>$task['run']<span style="font-family:宋体">为</span>INSTALL_TASK_RUN_IF_REACHED<span style="font-family:宋体">的任务除外(因为具备这样旗标的任务在每个安装请求中都需要运行,但也只运行一次)</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">install_run_task($task, &amp;$install_state)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">运行单个任务,根据任务类型进入不同的执行流程,逻辑较简单,请参考本系列批处理及表单相关主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">安装任务定义及任务回调开发:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">任务定义来自安装系统(见</span>install_tasks($install_state)<span style="font-family:宋体">函数)以及</span>profile<span style="font-family:宋体">扩展的任务钩子,以下以任务钩子的形式对其进行详细说明:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">任务钩子:</span></span></span></span></span></p> <pre> <code class="language-php">/** * 必须放置在所用安装配置扩展的install文件中 * 即安装配置扩展根目录的$profile_name.install文件中 * * @param $install_state */ function hook_install_tasks(&amp;$install_state) { $tasks = []; //键名表示任务名 $tasks['yunke_task_one'] = [//以下所有项都是可选的 //一个字符串值,表示任务的类型,有三种可能的值: //normal:表明是一个常规任务,任务回调做处理并可选的返回主题化输出,这是默认值 //batch:表明任务回调将返回批处理定义(batch_set()的参数),这种情况下,待返回后,安装器将自动运行批处理任务 //form:表明任务涉及表单流程,这种情况下,任务回调不是一个回调,而是一个表单类,安装器将启动表单处理流程 'type' =&gt; 'normal', //值为执行任务的回调,只要是回调均可(可以是数组、class::method、函数名等形式) //如果本设置项省略,将采用本任务的键名(比如这里是yunke_task_one) //如果是需要多个任务采用相同回调,那么就必须设置该项了,根据任务类型不同,回调有所区别 //如果任务类型为批处理batch: // 则回调仅有一个参数$install_state,如需改变则应以引用接收,但通常无需改变 // 须返回一个批处理定义,或多个批处理定义构成的数组,也可以返回NULL或FALSE //如果任务类型为normal: // 则回调仅有一个参数$install_state,通常应以引用接收 // 如果回调改变了$install_state['parameters']则当前请求会结束并重定向到新页面 // 如果回调设置$install_state['task_not_complete']为true,则表示工作未结束,会在本次或后续请求中再次执行 // 如有再次执行的情况,中间数据可以保存到状态系统或会话中 // 如果回调设置$install_state['stop_page_request']为true,则当前请求会结束,这很少见,一般用在批处理中 // 如果回调不返回任何值,也不修改$install_state,则系统继续执行下一个任务 // 如果有消息需要显示给用户,可以以渲染数组方式返回,此时当前请求会停止,如果同时设置了重定向那么重定向优先 //如果任务类型为表单form: // 此时该项值应为表单类的类名而不是回调,表单类和普通表单类无异;$install_state通过表单状态对象以引用方式传递 // 得到方法为:$formState-&gt;getBuildInfo()['args'][0],得到的是全局变量$install_state的引用,所以可以修改 // 也可以在表单方法中使用“global $install_state;”取得 // 在表单中不能通过表单方式设置重定向,而应以$install_state['parameters']方式设置 'function' =&gt; 'yunke', //在安装页面(默认在左侧边栏)显示给用户的人类可读的任务名,不设置将不显示,默认值为NULL 'display_name' =&gt; t('Choose language'), //一个布尔值,控制任务是否显示给用户,比如设置了display_name项,但在某些条件下又不需要显示,就需要用到该项了 //默认值视display_name项而定,为:!empty($task['display_name']) //详见函数:install_tasks_to_display($install_state) 'display' =&gt; TRUE, //值为一个常量,表示任务以哪种方式运行,有三种可能的值: //INSTALL_TASK_RUN_IF_NOT_COMPLETED:默认值,表明任务将运行一次 //INSTALL_TASK_SKIP:指示在当前请求中,任务将不运行,当某条件满足时可以设置该值跳过任务 //INSTALL_TASK_RUN_IF_REACHED:指示任务在每个安装请求期间到达时都会运行,这很少用,主要用她执行启动相关的任务 'run' =&gt; INSTALL_TASK_RUN_IF_NOT_COMPLETED, ]; } </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">任务修改钩子:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装配置扩展</span>profile<span style="font-family:宋体">可以通过任务修改钩子修改定义好的全部任务,包括安装系统提供的任务,这使得</span>profile<span style="font-family:宋体">可以完全控制系统安装,任务修改钩子如下:</span></span></span></p> <p style="text-align:justify; text-indent:21pt">&nbsp;</p> <pre> <code class="language-php"> /** * 用于修改完整的任务列表 * 应放置在安装配置扩展的主文件中 * 即安装配置扩展根目录的$profile_name.profile文件中 * * @param $tasks 完整任务列表 * @param $install_state 安装状态数组 */ function hook_install_tasks_alter(&amp;$tasks, $install_state) { //对任务列表数组做修改 } </code></pre> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">安装配置扩展</span><a name="_Hlk45815969">profile</a></b><b><span style="font-family:宋体">的</span>info</b><b><span style="font-family:宋体">文件结构:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">前文已经提到</span>profile<span style="font-family:宋体">扩展的制作和模块类似,她们拥有相同的文件、目录结构,为了进一步讲解安装,这里先介绍一下</span>profile<span style="font-family:宋体">的</span>info<span style="font-family:宋体">文件结构。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这和模块一样,有</span>name<span style="font-family:宋体">、</span>description<span style="font-family:宋体">、</span>dependencies<span style="font-family:宋体">等,但有也区别,以下是一个完整示例和解释:</span></span></span></p> <pre> <code class="language-yaml"> name: Yunke #type:值为profile type: profile # 描述 description: 'Install profile example.' # install值为需要安装的模块的机器名构成的数组,如果是多语言网站会自动合并locale模块,且会自动合并核心必须模块,即required为true的模块 # 如果这里设置了,但系统中不存在模块文件,那么在安装的需求验证阶段会报错,无法继续安装 install: - node - history - block - breakpoint - ckeditor - color - config - comment - contextual - contact - menu_link_content - datetime - block_content - quickedit - editor - help - image - menu_ui - options - path - page_cache - dynamic_page_cache - big_pipe - taxonomy - dblog - search - shortcut - toolbar - field_ui - file - rdf - views - views_ui - tour - automated_cron #需要安装的主题扩展,可选,默认为['stark'] themes: - bartik - seven # 通过该键将本profile声明为发行版 distribution: #发行版的名称,默认为“Drupal”,如果设置了该项,那么安装过程将自动选择本profile,而跳过让用户选择 - name: 'yunke' # 可选,值为数组 在安装过程中提供一些覆写 - install: # 安装过程中可以使用自定义的主题,在该处提供自定义主题的扩展机器名,默认为seven - theme: 'seven' # 一个url路径,指示安装完成后跳转到哪里 - finish_url: '/' # 可选,指定站点的安装语言,如果设置了则安装过程中会跳过语言选择步骤 - langcode: 'en' # 可选,用于指定配置同步目录,默认值为:如果目录:$profile_path . '/config/sync'存在,将设置为该值,否则为NULL config_install_path: null version: '9.0.2' project: 'drupal' datestamp: 1594237482 </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">系统安装流程:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">系统默认安装任务列表按顺序如下(详见</span>install_tasks($install_state)<span style="font-family:宋体">函数):</span></span></span></p> <ul> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">选择语言</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">下载翻译,如果不需要下载则跳过,比如语言为英语</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">选择安装配置扩展</span>profile<span style="font-family:宋体">,如果设置了发行版或系统中仅有一个</span>profile<span style="font-family:宋体">,那么跳过</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">加载</span>profile</span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">需求验证,执行需求钩子,见下文</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">填写提交数据库连接信息,生成站点哈希值、配置同步目录,如果已存在有效的配置文件则跳过</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">验证数据库已经可用</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装基础系统,这包括安装系统、用户模块等,详见下文,已安装则跳过</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">完整启动系统,启动</span>session<span style="font-family:宋体">,此后的任务均是在一个完整的系统中运行了</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk46482662"><span style="font-family:宋体">安装</span>profile</a><span style="font-family:宋体">中指定的模块,包括所依赖的模块,详见下文</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装</span>profile<span style="font-family:宋体">中指定的主题</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装</span>profile<span style="font-family:宋体">扩展本身,这将直接调用模块安装器,在此任务中会安装全站可选配置</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">导入翻译,不需要则跳过</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">配置站点</span><span style="font-family:宋体">,显示站点配置表单,以供设置站点名称、维护账户用户名和密码、系统安装时间等,见下</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果指定了从配置安装系统(即指定了</span>$install_state['config_install_path']<span style="font-family:宋体">),则以上步骤中会取消下载翻译,以及</span>profile<span style="font-family:宋体">中指定模块、主题和</span>profile<span style="font-family:宋体">本身的安装,取而代之的是配置导入、配置翻译下载</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">执行</span>profile<span style="font-family:宋体">中的</span>install_tasks<span style="font-family:宋体">钩子,取得</span>profile<span style="font-family:宋体">定义的安装任务,并依次执行</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">进行安装完成后的翻译处理</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">执行安装完成的善后工作</span></span></span></li> </ul> <p style="text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">以上任务和顺序均可在安装配置扩展的任务修改钩子(</span>install_tasks<span style="font-family:宋体">)中进行调整</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在每一个安装请求中都会初始化出以上任务列表,系统会以从前到后的顺序,根据当前请求的系统状态选择性执行列表中的任务,换句话说,状态可以决定任务是否被执行,这就导致任务列表的顺序不一定就是安装执行流程的顺序,任务顺序和执行流程是不一样的概念,比如交互式安装是在多个请求中执行,就导致执行流程可以再次回到任务列表中之前的任务;总而言之,任务顺序和不停变化的状态一起决定着执行流程;用户界面所看到的流程和程序上的流程也不一样。</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">有些任务会在每个安装请求中都执行(如选择语言,执行不代表会有输出,这由任务回调决定),有些任务可以反复执行多次。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">安装任务:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这里我们挑选一些比较重要的任务来看一看:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">下载翻译:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">任务回调:</span>install_download_translation(&amp;$install_state)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果用户选择了一个非英语的安装语言,且翻译目录(默认为站点路径下的</span>files/translations<span style="font-family:宋体">)不存在或其中不存在对应的翻译文件,那么系统将确保翻译目录存在(不存在则创建),并启动下载,如果已经存在翻译文件了则什么也不做,重定向当前页面到自己(因为任务已经执行过,所以重定向后不会再次执行),相当于刷新,以便让页面用对应语言显示</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果系统不能自动下载翻译文件,将抛出错误并暂停,此时需要我们人工下载后放入翻译目录中,并刷新安装页面继续安装,这可能在网络故障或服务器繁忙时发生</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">选择安装配置扩展:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">任务回调:</span>install_select_profile(&amp;$install_state)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果能够自动选择,那么将自动选择,如果不能则显示选择表单给用户选择,表单类如下:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">Drupal\Core\Installer\Form\SelectProfileForm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">自动选择按以下次序进行:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、如果只有一个有效的</span>profile<span style="font-family:宋体">,那么就选她</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、在</span>URL<span style="font-family:宋体">参数中指定了一个有效的</span>profile<span style="font-family:宋体">,那么就选她</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、如果</span>profile<span style="font-family:宋体">被声明为分发版(</span>info<span style="font-family:宋体">文件中有</span>distribution<span style="font-family:宋体">键),那么就选她,如果有多个</span>profile<span style="font-family:宋体">均被声明为分发版,那么采用扫描到的第一个</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、如果仅有一个可见的</span>profile<span style="font-family:宋体">,那么就选她,可见是指其</span>info<span style="font-family:宋体">文件中没有设置</span>hidden<span style="font-family:宋体">键,或设置了但值为</span>false</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">验证系统需求:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">任务回调:</span>install_verify_requirements(&amp;$install_state)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">验证将执行以下项目:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、</span>profile <span style="font-family:宋体">声明要安装的模块(即</span>info<span style="font-family:宋体">文件中</span>install<span style="font-family:宋体">项值,含依赖和必要模块)是否都存在于文件系统中</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、执行</span>profile <span style="font-family:宋体">以及</span>profile <span style="font-family:宋体">声明要安装的模块的</span>requirements<span style="font-family:宋体">钩子,钩子参数为“</span>install<span style="font-family:宋体">”,注意:模块的安装可能发生在安装系统期间,也可能在系统安装后的正常使用期,这两种情况下,模块的该钩子执行环境是完全不一样的,如果在系统安装期,则站点配置文件、数据库均还不存在,也没有模块被安装,要判断这个时期可以用静态方法:</span>\Drupal\Core\Installer\InstallerKernel::installationAttempted</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、是否能建立站点的配置文件,站点默认配置文件(</span>settings.php<span style="font-family:宋体">)就是在该任务中建立的,但在其中还尚未加入数据库信息</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">收集数据库信息(任务名:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">install_settings_form</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">):</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">任务回调:属于表单类型的任务,没有回调,表单类:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">Drupal\Core\Installer\Form\SiteSettingsForm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">Drupal<span style="font-family:宋体">默认支持三种数据库:</span>mysql<span style="font-family:宋体">、</span>pgsql<span style="font-family:宋体">、</span>sqlite<span style="font-family:宋体">,我们可以在以下目录:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">根目录</span>/drivers/lib/Drupal/Driver/Database</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">或模块的</span>/src/Driver/Database<span style="font-family:宋体">目录中放入数据库驱动以扩展更多所支持的类型,详见以下函数:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">drupal_get_database_types()</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该任务依据数据库类型产生配置表单,提交验证(实测可连接)通过后将连接信息写入配置文件</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">此外该任务还产生配置文件中的哈希盐值、配置同步目录</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">关于配置文件写入,这里有一个工具函数:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">drupal_rewrite_settings($settings = [], $settings_file = NULL)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该函数很通用,且常用,几乎是</span>CMS<span style="font-family:宋体">系统必备,使用方法示例如下:</span></span></span></p> <pre> <code class="language-php"> require_once DRUPAL_ROOT . '/core/includes/install.inc'; $settings['settings']['name'] = (object) [ 'value' =&gt; 'yunke', //变量值,可以是多维数组 'required' =&gt; TRUE, //是否真实写入,通常应该设置为true 'comment' =&gt; 'some comment',//可选,一个关于该变量的注释 ]; $var1 = ['a' =&gt; 'x', 'b' =&gt; ['m', 'n'], 'c' =&gt; 3]; $settings['var1'] = (object) [ 'value' =&gt; $var1, 'required' =&gt; TRUE, 'comment' =&gt; '多维数组的注释', ]; $settings['var2'] = ['a' =&gt; $settings['var1']];//将产生变量$var2['a'] $settings['var3'] = ['x', 'y']; //这种方式无效 $settings_file = DRUPAL_ROOT . '/yunke.php'; //配置文件需要预先创建 drupal_rewrite_settings($settings, $settings_file); //写入时会尽可能覆写存在的值,否则以追加方式添加变量 </code></pre> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">安装基本系统:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">任务回调:</span>function install_base_system(&amp;$install_state)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装基本系统,为后续安装打下基础</span></span></span></p> <ul> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">首先创建数据库表</span> <span style="font-family:宋体">“</span>config<span style="font-family:宋体">”,这是系统创建的第一个数据库表,然后安装核心提供的配置,其中的配置对象“</span>core.extension<span style="font-family:宋体">”将用来记录模块、主题的安装情况,为后续安装提供基础,就是在此时将</span>profile<span style="font-family:宋体">信息保存到其中的</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装系统模块,这是被安装的第一个模块,数据库中将创建这些数据库表:“</span>key_value<span style="font-family:宋体">、</span>key_value_expire<span style="font-family:宋体">、</span>sequences<span style="font-family:宋体">、</span>sessions<span style="font-family:宋体">”,这将为状态储存、批处理、会话提供必须的储存能力,模块安装直接调用模块安装器进行,详见本系列模块安装篇</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">重建路由,这将创建锁系统所需的“</span>semaphore<span style="font-family:宋体">”数据表、储存路由所需的“</span>router<span style="font-family:宋体">”</span> <span style="font-family:宋体">数据表</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在配置中保存系统所用的默认语言</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">为敏感目录创建“</span>.htaccess<span style="font-family:宋体">”文件,防止被终端直接访问</span></span></span></li> <li style="text-align: justify;"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装用户</span>user<span style="font-family:宋体">模块,这是被安装的第二个模块,将创建用户、角色相关数据库表</span></span></span></li> </ul> <p style="text-align:justify">​​​​​<br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">笔者语:在国内很多系统的安装,数据库都是由安装程序直接提供</span>SQL<span style="font-family:宋体">,但</span>Drupal<span style="font-family:宋体">完全不一样,其是由相关程序自行完成,全部建立在数据库操作</span>API<span style="font-family:宋体">之上,这带来极大的灵活性和简洁性,且易于维护</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">安装</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">profile</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">中指定的模块:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">任务回调:</span>install_profile_modules(&amp;$install_state)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">以批处理方式安装</span>profile<span style="font-family:宋体">中指定的模块,包含其依赖的模块,在安装过程中首先安装核心所必须要的模块,即</span>info<span style="font-family:宋体">文件中</span>required<span style="font-family:宋体">项为</span>true<span style="font-family:宋体">的模块;直接调用模块安装器安装:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal::service('module_installer')-&gt;install([$module], FALSE);</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注意:第二个参数为</span>false<span style="font-family:宋体">,这意味着在安装过程中并不会考虑模块间的依赖关系;在整个安装过程中,模块的需求检查钩子会被运行两次,一次是系统安装初期,第二次是在该步骤中,因此注意在钩子中一定不要去检查模块依赖,这些系统会自动检查</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">配置站点(任务名:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">install_configure_form</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">):</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">任务回调:属于表单类型的任务,没有回调,表单类:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">Drupal\Core\Installer\Form\SiteConfigureForm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于在安装后,设置站点基本属性,有:站点名、邮件、维护账户的用户名和密码、国家、时区;在配置文件是可写的情况下还会提醒将其设置为只读</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果选择了自动检查更新,那么会安装更新</span>update<span style="font-family:宋体">模块,如果进一步点选了接受邮件通知,那么会设置更新模块在有更新时去发送邮件</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">此外,会在状态系统中记录系统安装的时间:</span></span></span></p> <pre> <code class="language-php">\Drupal::state()-&gt;set('install_time', $_SERVER['REQUEST_TIME']); \Drupal::state()-&gt;get('install_time'); </code></pre> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">安装完成的善后工作:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">任务回调:</span>function install_finished(&amp;$install_state)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这是安装过程执行的最后一个任务,处理以下内容:</span></span></span></p> <ul> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在核心扩展配置对象中,设置</span>profile<span style="font-family:宋体">的权重为</span>1000<span style="font-family:宋体">,这样可以让其定义的修改钩子最后执行(除非人为设置其他模块的权重超过</span>1000<span style="font-family:宋体">),意味着拥有最高优先级</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">重建路由,让所有路由信息完备可用</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">运行一次计划任务,让定义了计划任务的模块在安装后及时执行一次</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">登录维护账户,让用户在安装后可以直接登录系统</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">通过状态消息,显示一个恭喜消息“</span>Congratulations, you installed @drupal!<span style="font-family:宋体">”</span></span></span></li> </ul> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">完整制作</span>profile</b><b><span style="font-family:宋体">扩展和分发版:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">前文已讲到分发版就是在</span>profile<span style="font-family:宋体">扩展的</span>info<span style="font-family:宋体">文件中有特殊声明而已,不必多讲;再一次总结,</span>profile<span style="font-family:宋体">的开发制作和模块无异,文件、目录结构都相同,区别如下:</span></span></span></p> <ul> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">分发版自定义的</span>profile<span style="font-family:宋体">扩展放置在系统根目录的</span>profiles<span style="font-family:宋体">文件夹中</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">info<span style="font-family:宋体">文件有额外功能,详见前文的注释</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">profile<span style="font-family:宋体">提供安装任务钩子(</span>install_tasks<span style="font-family:宋体">),其位于</span>profile <span style="font-family:宋体">根目录的“</span>$profile .install<span style="font-family:宋体">”文件中,其修改钩子位于主文件中,即“</span>$profile . profile<span style="font-family:宋体">”,模块无法提供该钩子</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">profile<span style="font-family:宋体">配置目录提供的配置对所有模块具有覆写功能,但不能覆写活动配置,详见配置安装器的方法:</span>\Drupal\Core\Config\ConfigInstaller::getConfigToCreate<span style="font-family:宋体">,这种覆写不只发生在系统安装期间,在系统安装后的站点运行期间,新安装模块时,依然会覆写;在系统安装期间,模块提供的简单配置的覆写被立即应用,配置实体的覆写更新发生在</span>profile<span style="font-family:宋体">安装时</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在一个已安装</span>drupal<span style="font-family:宋体">实例上安装模块时,安装默认配置时会一并安装可选配置,但在实例安装过程中并不会这样做,而是全部模块安装结束后再统一安装所有模块的可选配置</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在核心扩展配置(配置对象名:</span>core.extension<span style="font-family:宋体">)中,</span>profile<span style="font-family:宋体">的权重被设置为</span>1000<span style="font-family:宋体">,这就使得</span>profile<span style="font-family:宋体">提供的修改钩子在普通模块提供的修改钩子之后执行,这意味着具有更高优先级,除非人为设置其他模块的权重大于</span>1000.</span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">profile<span style="font-family:宋体">扩展本身的安装和模块安装无异,她同样可以提供“</span>hook_install()<span style="font-family:宋体">”钩子,该钩子通常用于执行和整个系统安装相关的功能,这是除安装任务之外</span>profile<span style="font-family:宋体">可以控制系统安装的另一个方法</span></span></span></li> </ul> <p style="text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">从配置安装:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">假设经过一番努力我们使用</span>Drupal<span style="font-family:宋体">制作了一个活动报名系统,小伙伴们体验后感觉很好很优秀,希望自己也有一个,此时怎么办呢?这就需要</span>profile<span style="font-family:宋体">从已存站点的配置来安装系统。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如希望</span>profile<span style="font-family:宋体">从已存站点的配置来安装系统,则在其根目录中建立配置同步目录,默认为“</span>/config/sync<span style="font-family:宋体">”(如果不是默认值则需要在</span>info<span style="font-family:宋体">文件中指明),并在其中存放从已存站点导出的配置</span>yml<span style="font-family:宋体">文件,注意导出的配置中,如果核心扩展配置文件“</span>core.extension.yml<span style="font-family:宋体">”中指定的</span>profile<span style="font-family:宋体">和制作的</span>profile<span style="font-family:宋体">不同名,那么需要一并修改(共有两处值),否则安装时会导致导入配置验证失败,有些配置文件中可能存在</span>UUID<span style="font-family:宋体">或默认配置哈希,为了避免发行版实例间冲突,需要移除,可在命令行中使用如下命令:</span></span></span></p> <p style="text-align:justify; text-indent:21pt">&nbsp;</p> <pre> <code class="language-bash">find /path/to/PROFILE_NAME/config/install/ -type f -exec sed -i -e '/^uuid: /d' {} \; find /path/to/PROFILE_NAME/config/install/ -type f -exec sed -i -e '/_core:/,+1d' {} \; </code></pre> <p style="text-indent:21.2pt; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">对于</span>Mac OSX<span style="font-family:宋体">用户请使用以下命令:</span></span></span></p> <p style="text-align:justify; text-indent:21pt">&nbsp;</p> <pre> <code class="language-bash">find /path/to/PROFILE_NAME/config/install/ -type f -exec sed -i '' '/^uuid: /d' {} \; find /path/to/PROFILE_NAME/config/install/ -type f -exec sed -i '' '/_core:/{N;d;}' {} \; </code></pre> <p style="text-indent:21.2pt; text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">同时从源站点复制必须要的模块、主题等到新安装包;如果源站点是多语言的,建议一并提供翻译文件,避免安装时自动下载翻译遇到服务器错误相关问题而导致安装过程中断。</span></span></span></p> <p style="text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">默认内容:</span></span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果发行版需要默认内容,可以下载“</span>default_content<span style="font-family:宋体">”模块,下载地址:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">https://www.drupal.org/project/default_content</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">下载后放入</span>profile<span style="font-family:宋体">根目录下的</span>modules<span style="font-family:宋体">目录中</span><span style="font-family:宋体">,在“</span>default_content<span style="font-family:宋体">”模块中提供默认内容(详见该扩展介绍),就绪后内容模块的安装方式依据系统安装方式的不同,有以下两种:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">系统从配置安装:通过安装任务的方式来安装默认内容模块</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">系统不从配置安装:在</span>profile<span style="font-family:宋体">扩展的</span>hook_install()<span style="font-family:宋体">钩子中调用模块安装器来安装默认内容模块</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">可以参见默认安装包中提供的内容演示模块</span>demo_umami</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">核心自带演示</span>Profile</b><b><span style="font-family:宋体">扩展简介:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">核心自带了三个</span>profile<span style="font-family:宋体">扩展,位于目录“</span>core/profiles<span style="font-family:宋体">”中(该目录同时也提供了一些在</span>UI<span style="font-family:宋体">中不可见的</span>profile<span style="font-family:宋体">,用于测试目的),标准安装和最小化安装均比较简单,你可以看一看演示</span>profile <span style="font-family:宋体">:“</span>demo_umami<span style="font-family:宋体">”,观察她是如何控制安装,以及如何导入站点内容,本篇内容足以让你理解她的细节,在此不多讲解。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">补充:</span></b></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、官网相关文档:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">创建分发版:</span>https://www.drupal.org/docs/distributions/creating-distributions</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">下载共享的分发版:</span>https://www.drupal.org/project/project_distribution</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、为了安全起见,安装后记得将站点配置文件设置成只读状态</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、在系统安装以后查询当前安装的</span>Profile<span style="font-family:宋体">方法:</span>$profile = \Drupal::installProfile()</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、如果安装期间网络连接中断,或不小心关闭了浏览器,导致没有完整完成安装怎么办呢?只需要刷新或无查询参数打开安装脚本继续就行了</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">5<span style="font-family:宋体">、判断当前系统是否正处于安装过程中,请使用以下代码:</span></span></span></p> <pre> <code class="language-php">\Drupal\Core\Installer\InstallerKernel::installationAttempted()</code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">6<span style="font-family:宋体">、安装期间会扫描模块目录,所有模块均会被扫描,如果有模块的</span>info<span style="font-family:宋体">文件错误,会导致抛出异常,比如在</span>info<span style="font-family:宋体">中同时出现以下两行:</span></span></span></p> <pre> <code class="language-yaml">core: 8.x core_version_requirement: '~8.8 || ^9' </code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">就会抛出不允许有</span>core<span style="font-family:宋体">键的异常</span></span></span></p> <p style="text-align:justify">&nbsp;</p> <p style="text-align:justify">&nbsp;</p> </div> <section data-drupal-selector="comments" class="comments"> <h2 class="comments__title">反馈互动</h2> <div class="add-comment"> <div class="add-comment__form"> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=212&amp;2=comment&amp;3=comment" token="21QyDjuz5iPBmhAfIRELqPXyoUz65_ZOQhGaaKlrOes"></drupal-render-placeholder> </div> </div> </section> Fri, 31 Jul 2020 00:16:11 +0000 云客 212 at http://indrupal.com http://indrupal.com/node/212#comments 151. 系统更新 http://indrupal.com/node/210 <span>151. 系统更新</span> <span><span>云客</span></span> <span><time datetime="2020-07-24T08:52:40+08:00" title="2020-07-24 08:52 星期五">周五, 07/24/2020 - 08:52</time> </span> <div class="text-content clearfix field field--name-body field--type-text-with-summary field--label-hidden field__item"><p><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">“我的孩子,你是我生命的延续,我终会归于泥土,你将永世长存,让我来助你成长”——开发者。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">术语解释:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在开始本篇前,我们需要对以下词汇做一个清晰的解释:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">更新</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">Update</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">Drupal<span style="font-family:宋体">遵循语义化版本控制(见</span>https://semver.org/lang/zh-CN/<span style="font-family:宋体">),更新是指同一个主版本(</span>major version<span style="font-family:宋体">)下的小版本(</span>minor version<span style="font-family:宋体">)升级,比如从</span>D8.3.2<span style="font-family:宋体">升级到</span>8.9.0<span style="font-family:宋体">,这里的小版本从</span>3<span style="font-family:宋体">升到</span>9<span style="font-family:宋体">,这就是本篇的主要内容</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">升级</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">Upgrade</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">指大版本的升级,比如</span>Drupal 7<span style="font-family:宋体">升级到</span>Drupal 8<span style="font-family:宋体">,或</span>D8<span style="font-family:宋体">升级到</span>D9</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">迁移</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">Migrating</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span></span></span></p> <p align="left" style="text-align:left; text-indent:0cm"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">指系统在不同主机上的迁移,如从本地计算机迁移到线上服务器、从一个服务器迁移到另一个。</span></span></span></p> <p align="left" style="text-align:left; text-indent:0cm"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注意:</span></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">Drupal<span style="font-family:宋体">核心在</span>Migration<span style="font-family:宋体">组中提供提供了三个模块:</span>migrate<span style="font-family:宋体">、</span>migrate_drupal<span style="font-family:宋体">、</span>migrate_drupal_ui<span style="font-family:宋体">,她们既不用于更新,也不用于系统迁移或系统升级,而是专用于“系统内容”的迁移,系统内容迁移和系统迁移是两回事,比如将帝国</span>cms<span style="font-family:宋体">的数据迁移到</span>Drupal<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">系统更新概述:</span></b></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">有过很多年经验的软件开发者会强烈的感觉到软件是有生命的,她的生命和开发者融为一体,离开了开发者即便软件能正常使用,那也只能算是工具,是生命就需要成长,更新即软件成长,生命的成长须一步一步来,没有哪个生命是跳跃式成长的,缔造出一款生命力强劲的软件是每个开发者都渴望的。</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这里“成长须一步一步来”是关键,表现在软件上便是每一次更新都是建立在上一次更新基础之上的,换句话说是从上一次更新后的状态开始进行新的更新,如果开发者在设计一次更新时,不以上一次更新后的结果为基础,那么事情将变的非常复杂,甚至是不可能完成的任务,因此在更新这条路上,我们需要为每一次更新建立序号,以表明各次更新的先后关系,在设计每一次更新时,仅需以上一次更新后的状态为基础即可,软件需要携带从初始版本到新版之间设计的每一次更新,这样当用户得到新版软件时,就可以从当前版本按次序依次执行那些更新,这样不管用户的当前版本是什么,都能保证正确的更新到最新版。</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">具体到</span>Drupal<span style="font-family:宋体">,更新任务分两大部分:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">代码更新</span></span></span><span style="font-family:宋体">:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">目前需用户自行下载核心代码或模块代码上传到服务器,将来可能采用自动下载</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">数据库更新</span></span></span><span style="font-family:宋体">:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">又可分为模式更新和数据更新,目前数据库更新暂时只有模式更新和数据更新两种类型。</span></span></span></p> <p style="text-align:justify">&nbsp;</p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模式</span>schema<span style="font-family:宋体">是指数据库中的数据结构,模式更新即数据结构更新,由模式更新钩子“</span>hook_update_N<span style="font-family:宋体">”进行,钩子函数名中的</span>N<span style="font-family:宋体">部分是一个从小到大变化的数字,每一次更新均实现一次该钩子,将</span>N<span style="font-family:宋体">指定为一个更大的唯一的值,其代表更新的先后关系,每次更新完成后,</span>N<span style="font-family:宋体">值将作为模块当前的模式版本号,所有模块的模式版本号保存在以下位置:</span></span></span></p> <pre> <code class="language-php">\Drupal::keyValue('system.schema')-&gt;getAll()</code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">通过记录模式版本号就能知道已经执行过哪些更新钩子,当前处于更新链路的哪个位置,模式更新钩子详见下文。</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">数据更新即可能出现的数据本身的更新,由数据更新钩子“</span>hook_post_update_NAME<span style="font-family:宋体">”进行,其中</span>NAME<span style="font-family:宋体">部分是一个变化的字符串标识,同模式更新钩子中的</span>N<span style="font-family:宋体">有类似作用,其字母排序顺序代表更新的先后顺序,已经执行过的数据更新钩子保存在以下位置:</span></span></span></p> <pre> <code class="language-php">\Drupal::keyValue('post_update')-&gt;getAll();</code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">其中的“</span>existing_updates<span style="font-family:宋体">”键保存着已被执行过的数据更新钩子函数名。</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">数据更新钩子详见下文</span> </span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">某个</span>Drupal<span style="font-family:宋体">大版本的(核心和模块)代码中需要携带该大版本实现过的全部更新钩子,这样才能保证用户系统不论处于该大版本的哪一个小版本都能进行正常更新,这些更新钩子通常在大版本升级时才从代码中移除,移除也不是直接删除那么简单,详见下文。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">执行更新:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">更新可以有几种方式:</span>Composer<span style="font-family:宋体">、</span>drush<span style="font-family:宋体">(已不赞成)、手动更新,本节以手动更新为例。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">不论是核心还是模块更新,系统更新的流程均如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">一、开启维护模式(不是必须的,但建议开启),下载并替换旧的代码。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">二、如果更新发布页有特别说明,则按说明进行额外处理,如修改“</span>.htaccess<span style="font-family:宋体">”、配置文件等。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">三、访问站点根目录的系统更新脚本(</span>update.php<span style="font-family:宋体">)进行数据库更新。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">通常通过以上三步就完成了一次系统更新,如果出现异常就需要查看日志进行人工排查处理(强烈推荐更新前备份,以便发生异常时安全的恢复数据)。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">访问更新脚本必须满足以下两个条件之一:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、在站点配置文件中,</span>$settings['update_free_access']<span style="font-family:宋体">项的值为</span>true<span style="font-family:宋体">,这代表任何人可以执行更新,默认为</span>false<span style="font-family:宋体">,当更新结束后应该改回</span>false<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、当前登录账户具备管理软件更新的权限(权限名:</span>Administer software updates<span style="font-family:宋体">),该权限由系统模块提供;如果是公司团队那么可以建立一个更新账号并分配该权限,如果是个人站长,由于维护账号具备一切权限,通常直接登录维护账号执行更新</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">更新脚本</span>update.php<span style="font-family:宋体">和</span>index.php<span style="font-family:宋体">大体类似,所不同的是继承微调了</span>DrupalKernel<span style="font-family:宋体">核心,最终将执行以下数据库更新控制器:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\system\Controller\DbUpdateController::handle</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该控制器会和用户交互执行以下步骤:</span></span></span></p> <pre> <code> 检查安装需求 (完成) 概览 (活跃) 审查更新 运行更新 审查日志 </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这些步骤详见下文,所有的更新钩子均在批处理流程中执行。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注:系统提供了一个更新路由,如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">路由名:</span>system.db_update</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">路径:</span>/update.php/info</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该路由仅用来产生</span>URL<span style="font-family:宋体">,当被访问时,系统入口并不是“</span>index.php<span style="font-family:宋体">”,而是更新脚本“</span>update.php<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在详解更新逻辑前我们先了解下几个需要用到的服务。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">数据更新注册服务</span></b><span style="font-family:宋体">:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>update.post_update_registry</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Update\UpdateRegistry</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该服务用于数据更新钩子的相关操作,如记录已执行的数据更新钩子,得到尚未执行的数据更新钩子等,在类设计上还可以用于其他类型的更新钩子操作。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">由服务工厂“</span>update.post_update_registry_factory<span style="font-family:宋体">”实例化,各方法解释如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function getRemovedPostUpdates($module)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">获取模块被移除的数据更新钩子函数名,即调用其“</span>removed_post_updates<span style="font-family:宋体">”钩子(详见数据更新钩子一节),传递一个模块机器名,如果该模块实现了钩子:“</span>{$module}_removed_post_updates<span style="font-family:宋体">”,那么调用钩子并返回钩子的返回值,如无实现则返回空数组,该钩子用于指明不再被使用的“</span>post_update<span style="font-family:宋体">”钩子(即数据更新钩子),其返回一个数组:键名是该模块已不再使用的“</span>post_update<span style="font-family:宋体">”更新钩子</span><span style="font-family:宋体">函数名,键值为不再使用此更新钩子的第一个模块稳定版本的版本号,没有时返回空数组;一旦被声明移除,就需要从代码中移除。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function getAvailableUpdateFunctions()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">从用户定义的函数空间中,取得某类型(默认为数据更新</span>post_update<span style="font-family:宋体">)有效的更新钩子(函数),有效是指提供更新函数的模块已开启,且函数名满足以下格式:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">“</span>moduleName_updateType_funName<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">其中</span>moduleName<span style="font-family:宋体">为模块机器名,</span>updateType<span style="font-family:宋体">为更新类型,</span>funName<span style="font-family:宋体">为更新标识;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果是“</span>post_update<span style="font-family:宋体">”类型的更新(数据更新),返回值会包含已执行的数据更新钩子,另外会检查该钩子对应的移除声明钩子“</span>removed_post_updates<span style="font-family:宋体">”,如果已被声明为移除状态但还出现在代码中,那么将抛出错误。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">取得的更新函数以字母序排序(</span>sort<span style="font-family:宋体">函数),这意味着函数名代表了更新函数的执行顺序</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function getPendingUpdateFunctions()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">找出所有模块尚未执行的(挂起的)数据更新钩子,返回一个由更新钩子函数名构成的数组,已经排除了被执行过的数据更新钩子,以及</span>hook_removed_post_updates()<span style="font-family:宋体">钩子指定的已失效的数据更新钩子,结果经过</span>sort<span style="font-family:宋体">函数排序</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function getPendingUpdateInformation()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">得到所有模块挂起的数据更新钩子信息,该方法返回一个多维数组,第一级键名为模块名,第二级键名有两个:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>pending</i><i><span style="font-family:宋体">:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">代表该模块挂起的更新,其值为数组,键名为数据更新钩子函数名的尾名(</span>post_update_<span style="font-family:宋体">之后的部分,即</span>NAME<span style="font-family:宋体">部分),键值为描述,来自钩子函数的注释文档块,已经去掉了换行和注释符号,注意:并未将其包装成翻译对象</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>start</i><i><span style="font-family:宋体">:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">指示将要执行的第一个更新钩子函数,值为数据更新钩子中的</span>NAME<span style="font-family:宋体">部分,如无该项表示没有更新会执行</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function registerInvokedUpdates(array $function_names)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">保存已经执行的数据更新钩子函数,以指示不被再次执行;传递已执行的数据更新钩子函数名构成的数组,她们将和以前保存的信息一起合并储存在以下键值储存中:</span> </span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal::keyValue('post_update')-&gt;get('existing_updates', [])</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">一旦保存(注册)将不会再被执行;该方法返回</span>$this<span style="font-family:宋体">以便链式调用。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function getModuleUpdateFunctions($module_name)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">得到某模块的全部数据更新钩子函数,包含已执行过的,传递模块机器名,返回函数名构成的数组</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function scanExtensionsAndLoadUpdateFiles()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">扫描并加载所有已启用模块和安装配置扩展的数据更新钩子,即</span>include_once<span style="font-family:宋体">存放她们的</span>php<span style="font-family:宋体">文件;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该类的设计不仅用于数据更新钩子,也可用于其他类型的更新,数据更新仅是更新类型中的一种,更新类型在类属性“</span>updateType<span style="font-family:宋体">”中指定,默认为“</span>post_update<span style="font-family:宋体">”(数据更新钩子)</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function filterOutInvokedUpdatesByModule($module)</span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">从已执行数据更新钩子注册记录中移除某个模块所有已执行的数据更新钩子,这用于模块卸载时,传递模块机器名,无返回值。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">注意:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模块在安装阶段,会将其所有数据更新钩子注册成为已执行,这里的所有是指不管是已执行或未执行,同时包含移除钩子的移除声明。在模块卸载时,将从注册数据中删除该模块的所有已注册“数据更新钩子”。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">裸页面渲染器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>bare_html_page_renderer</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Render\BareHtmlPageRenderer</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于裸页面(</span>bare HTML page<span style="font-family:宋体">)的渲染,裸页面指仅包含核心内容(主内容)的页面,不包含页头、页尾、菜单等周边块,常见的裸页面如下:</span></span></span></p> <p style="text-align:justify; text-indent:21pt">&nbsp;</p> <pre> <code>安装页面: install.php 更新页面:update.php 授权页面:authorize.php 维护模式:maintenance mode 异常提示页: exception handlers </code></pre> <p style="text-indent:14.15pt; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">裸页面渲染器仅一个方法:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">public function renderBarePage(array $content, $title, $page_theme_property, array $page_additions = []);</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数含义如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>$content</i><span style="font-family:宋体">:主内容的渲染数组</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>$title</i><span style="font-family:宋体">:页面标题</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>$page_theme_property</i><span style="font-family:宋体">:裸页面采用的主题钩子,即“</span>#theme<span style="font-family:宋体">”属性的值</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>$page_additions</i><span style="font-family:宋体">:需要显示的额外内容(需要主题支持才能正常显示),默认会附加状态消息,但我们可以设置该参数“</span>$page_additions['#show_messages']<span style="font-family:宋体">”的值为</span>false<span style="font-family:宋体">而取消状态消息</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在使用裸页面渲染器时,我们通常需要先提供一个主题钩子,这里以维护页主题钩子为例提供一个裸页面渲染器的使用示例(在控制器中执行):</span></span></span></p> <pre> <code class="language-php"> $main = [ '#markup' =&gt; '裸页面示例', ]; $title = '裸页面标题示例'; $response = \Drupal::service('bare_html_page_renderer')-&gt;renderBarePage($main, $title, 'maintenance_page'); return $response; </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">&nbsp;&nbsp; </span></span><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注:系统更新页默认使用的是</span>seven<span style="font-family:宋体">主题,主题钩子来自</span>seven<span style="font-family:宋体">主题覆写</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">更新过程如下:</span></b></span></span></p> <ul> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">加载所有已安装模块的“</span>module.install<span style="font-family:宋体">”文件,但更新代码后与系统不兼容的模块除外,更新序号(</span>schema version<span style="font-family:宋体">)不大于</span>-1<span style="font-family:宋体">的模块也不会加载(更新序号在模块被安装时记录,卸载时被删除,执行更新后被更新,如其等于</span>-1<span style="font-family:宋体">,即常量</span>SCHEMA_UNINSTALLED<span style="font-family:宋体">,则表示模块已被卸载),这意味着这样的模块不会得到更新</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">执行更新需求检查(执行需求检查钩子),如果出现需求错误将显示报告页面,如果仅为警告信息那么当网址中有查询参数“</span>continue<span style="font-family:宋体">”且值等效为</span>true<span style="font-family:宋体">时,允许跳过需求检查页面继续执行更新,此阶段称为需求检查阶段,页面内容由元素类型为“</span>status_report<span style="font-family:宋体">”的渲染元素负责呈现</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">需求检查通过后,将显示一个概览提示页(称为</span>info<span style="font-family:宋体">阶段),给用户提示一些重要信息,比如备份建议、更新手册查看链接等</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">显示等待执行的(挂起的)更新函数(称为</span>selection<span style="font-family:宋体">阶段),更新描述来自更新函数的注释文档块(去掉了换行和注释符号,没有采用翻译机制),已经解析过依赖和兼容性,如有异常消息将通过状态消息显示给用户,如果没有挂起的更新,则更新过程结束。</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">开始批处理设置和运行阶段,在执行更新前,不管系统是否处于维护模式,都总是将系统置为维护模式,之后所有更新均在批处理流程中进行</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">执行模式更新钩子,全部模式更新钩子均在批处理回调函数</span>update_do_one($module, $number, $dependency_map, &amp;$context)<span style="font-family:宋体">中执行</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">所有模式更新钩子执行完毕后,继续执行数据更新钩子(如果有的话),在数据更新钩子执行前系统会失效所有缓存,数据更新钩子在批回调</span>update_invoke_post_update($function, &amp;$context)<span style="font-family:宋体">中执行</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">批处理结束后执行</span>\Drupal\system\Controller\DbUpdateController::batchFinished<span style="font-family:宋体">,这将清失效全部缓存,恢复之前的维护模式状态</span></span></span></li> <li style="text-align:justify" value="50"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">最后一步显示更新处理结果(称为</span>results<span style="font-family:宋体">阶段),如果在该页面出现了异常提示,那么需要通过日志排查错误原因,并手动处理,处理后如果有尚未执行的更新,则需要继续执行</span></span></span></li> </ul> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">需求检查钩子:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">钩子名:</span></span></span><a name="_Hlk41406228"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">requirements</span></span></a></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于模块在安装、更新时进行需求检查,也用于模块正常使用期间进行状态报告,该钩子必须放置在“</span>module_name.install<span style="font-family:宋体">”文件</span><span style="font-family:宋体">中。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回值是一个数组,键名是被检查项目的标识名称,该名称随意,但需要保证唯一,建议采用模块名做前缀,键值为一个数组,各键含义如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>severity</i><i><span style="font-family:宋体">:</span></i></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">状态信息或需求不被满足的严重级别,值为四个常量之一:</span>REQUIREMENT_INFO<span style="font-family:宋体">(表示检查仅传递状态信息)、</span>REQUIREMENT_OK<span style="font-family:宋体">(需求成功满足)、</span>REQUIREMENT_WARNING<span style="font-family:宋体">(需求勉强满足,安装会进行但发出警告)、</span>REQUIREMENT_ERROR<span style="font-family:宋体">(错误,需求完全不能满足,终止安装);整个返回值中,只要有一个检查项目为错误级别,那么整体就被认为是错误级别。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>title</i><i><span style="font-family:宋体">:</span></i></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">需求项的显示名称,一个翻译对象</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>description</i><i><span style="font-family:宋体">:</span></i></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">需求或状态的描述信息,一个翻译对象</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>value</i><i><span style="font-family:宋体">:</span></i></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当前值(</span>version, time, level<span style="font-family:宋体">等),在参数为安装时,仅应表示版本号,如果不适用则不要去设置该项。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该钩子仅接收一个表示场景的参数,值为</span>install<span style="font-family:宋体">、</span>update<span style="font-family:宋体">、</span>runtime<span style="font-family:宋体">之一,含义如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i><span style="font-family:宋体">参数为“</span>install</i><i><span style="font-family:宋体">”时:</span></i></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">表示调用该钩子时,所属模块即将被安装,用于检查所属模块的安装需求是否能够得到满足,不满足将不被安装,注意:仅“将被安装的模块”的该钩子被调用,调用时模块尚未安装,钩子调用未必要求模块一定被安装,该钩子就是一个列子;钩子内部:不需要检查模块依赖、环境信息,这些应该通过模块的</span>info<span style="font-family:宋体">文件指定,系统会自动检查;模块的该钩子调用可能发生在系统安装时,此时配置文件还不存在,尚没有模块被安装,数据库无法使用,因此需要检查安装时机是系统建立后还是系统正在安装时(安装核心类有静态方法来判断当前是否处于系统安装时,详见本系列系统安装篇);如果需要一个类文件,那么需要直接去加载;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i><span style="font-family:宋体">参数为“</span>update</i><i><span style="font-family:宋体">”</span> </i><i><span style="font-family:宋体">时:</span></i></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">详见:</span>update_check_requirements()<span style="font-family:宋体">,调用该钩子时,所属模块已被安装,</span>update.php<span style="font-family:宋体">正在运行,用于执行更新前检查更新需求是否能被满足</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i><span style="font-family:宋体">参数为“</span>runtime</i><i><span style="font-family:宋体">”</span> </i><i><span style="font-family:宋体">时:</span></i></span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">调用该钩子时,所属模块已被安装,属于运行时检查,用于进行状态报告,不局限于请求检查,比如维护任务、安全更新、站点迁移后的检查等,在(</span>/admin/reports/status<span style="font-family:宋体">)状态报告页可见其返回的信息。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模式</span>schema</b><b><span style="font-family:宋体">更新:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">钩子函数签名为:</span>hook_update_N(&amp;$sandbox)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于在模块小版本间执行模式</span>schema<span style="font-family:宋体">更新,如数据库结构调整、类型变化等(数据更新有单独的钩子,见下文),其实更适合的钩子名应为“</span>hook_schema_update_N<span style="font-family:宋体">”,但由于历史原因并没有这样做,钩子函数应放置在模块根目录的</span>module.install<span style="font-family:宋体">文件中,其中“</span>N<span style="font-family:宋体">”是一个数字,称为更新序号或模式号,同一个模块的更新钩子是按更新序号从低到高依次执行的,模式号“</span>N<span style="font-family:宋体">”由以下三部分组成:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">第一部分</span></span></span><span style="font-family:宋体">:占</span>1<span style="font-family:宋体">位或</span>2<span style="font-family:宋体">位,代表核心兼容性,比如</span>8<span style="font-family:宋体">为</span>Drupal 8<span style="font-family:宋体">,</span>10<span style="font-family:宋体">为</span>Drupal10<span style="font-family:宋体">,该约定必须遵守</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">第二部分</span></span></span><span style="font-family:宋体">:占</span>1<span style="font-family:宋体">位,代表模块的主版本号,该约定是可选的,可以采用</span>0-9<span style="font-family:宋体">的任一数字,但必须要有,建议采用模块主版本号,通常核心模块采用</span>drupal<span style="font-family:宋体">的次版本号,假设为</span>Drupal8.3<span style="font-family:宋体">,那么该位就是</span>3</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">第三部分</span></span></span><span style="font-family:宋体">:占</span>2<span style="font-family:宋体">位,代表从</span>01<span style="font-family:宋体">开始的从小到大的更新序号,最多容纳</span>99<span style="font-family:宋体">次更新</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">示例如:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">node_update_8001()<span style="font-family:宋体">:表示</span>Drupal 8.0.x <span style="font-family:宋体">中节点模块的第一个更新函数</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">module_update_8101()<span style="font-family:宋体">:模块版本</span> 8.x-1.x <span style="font-family:宋体">的第一个更新函数</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">module_update_8208()<span style="font-family:宋体">:模块版本</span> 8.x-2.x<span style="font-family:宋体">的第八个更新函数</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模式号</span>N<span style="font-family:宋体">被视为数字,以数字方式进行比较,每次更新设置的模式号必须由小到大(但不要求连续),系统在执行更新时也从小到大执行,在逻辑上每一次更新均以上一个更新函数的处理结果为基础。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在模块新安装时系统会在“</span>\Drupal::keyValue('system.schema')<span style="font-family:宋体">”中记录模块的最大模式号,之后每次更新完成时会设置新的模式号,记录值便是模块当前的模式号(</span>schema version<span style="font-family:宋体">),其指明了模块最后一个被执行的模式更新钩子,系统以此方式追踪更新,已执行过的模式更新钩子不再被执行;在卸载模块时会删除记录的模式号。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">最小模式号应大于常量:</span>\Drupal::CORE_MINIMUM_SCHEMA_VERSION<span style="font-family:宋体">(默认为</span>8000<span style="font-family:宋体">,不能等于),在模块生命周期中一旦丢失被记录的模式号,则按新安装对待,这可能引起严重问题,且修复工作困难。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">为避免</span>PHP<span style="font-family:宋体">超时,更新均在批处理流程中进行,但模式更新钩子并不直接作为批处理回调,真正作为批处理回调的是以下函数(模式更新钩子在该函数中执行):</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">update_do_one($module, $number, $dependency_map, &amp;$context)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数含义如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$module</span></span><span style="font-family:宋体">:模块机器名</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$number</span></span><span style="font-family:宋体">:模式号,即模式更新钩子中的</span>N<span style="font-family:宋体">部分</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$dependency_map</span></span><span style="font-family:宋体">:模式更新钩子之间可能有先后执行的需求,即有依赖关系(详见下文的模式更新依赖钩子),该参数指明本模式更新钩子依赖的其他模式更新钩子,是一个由模式更新钩子函数名构成的数组,已经过依赖排序,第一个元素是依赖链的根节点,即所有钩子都会依赖的那个模式更新钩子</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$context</span></span><span style="font-family:宋体">:为批处理上下文参数</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在模式更新钩子函数中,参数</span>$sandbox<span style="font-family:宋体">并不被传递完整的批处理上下文参数</span>$context<span style="font-family:宋体">,而是</span>$context['sandbox']<span style="font-family:宋体">,钩子内部必须以引用接收,如果钩子在一个请求中无法完成任务,需要多请求多次执行,那么可以设置参数项</span>$sandbox ['#finished']<span style="font-family:宋体">,这将被系统传递给批处理上下文参数的</span>$context['finished']<span style="font-family:宋体">,值为</span>0-1<span style="font-family:宋体">之间的浮点数,表示进度,详见批处理</span>API<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当模式更新钩子执行成功时,应返回一个字符串(或翻译对象),这将在更新脚本运行完成后显示给用户,如果没有需要显示的,也可返回</span>NULL<span style="font-family:宋体">或不返回;返回后系统会记录新的模式号。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当模式更新钩子执行遇到异常时,应抛出以下异常:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">Drupal\Core\Utility\UpdateException</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如:</span></span></span></p> <pre> <code class="language-php">use Drupal\Core\Utility\UpdateException; throw new UpdateException('Description of what went wrong'); </code></pre> <p style="text-indent:21.2pt; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">系统收到异常后会记录日志,并将本模式更新钩子视为执行不成功,不会记录新的模式号,一旦被视作不成功,那么后续依赖本模式更新钩子的所有其他模式更新钩子都不会被执行,连不相干的数据更新钩子也不会被执行。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在运行更新脚本(</span>update.php<span style="font-family:宋体">)时,全部模块所有挂起的模式更新钩子均会被列在审查更新页面,其注释文档块会被系统提取出来当做更新的描述(已去掉换行和注释符号),因此为了更好的用户体验,我们在实现该钩子时建议通过注释文档块注明其工作内容,但须注意这里并不采用翻译机制。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">有些资料将模式更新钩子称为模块的更新函数,这是因为其无法通过模块处理器派发,所以从狭义上讲她并不是钩子,但从广义上讲本系列也不否认其是钩子</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模式更新钩子的删除(更新断点):</span></b></span></span></p> <p style="text-indent:21.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">前文已经讲到了新发布的(核心和模块)代码需要携带实现过的全部更新钩子,这样才能保证用户系统不论处于哪一个版本都能依次进行更新,但这产生了一个新的问题:随着更新次数越来越多,新版携带的更新钩子函数将越来越多,文件将越来越大,这可能达到夸张的地步,它们对新版的发布形成了拖累,怎么解决呢?</span></span></span></p> <p style="text-indent:21.0pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">Drupal<span style="font-family:宋体">的思路是设置“更新断点”(注:官方尚未使用更新断点这个词汇,这里为了描述方便由云客提出),即当更新钩子达到一定量后删除一次,删除后的第一个版本称为断点版,其形成一个更新断点,被删除的模式更新钩子中最大的那一个的</span>N<span style="font-family:宋体">值即为断点号,断点版及其之后发布的更新版不携带断点号及之前的模式更新钩子,如果用户系统的模式号小于断点号(非小于等于),那么无法直接更新到最新版,因为缺失更新钩子,需要先更新到包含断点号对应的更新钩子的任一版本,然后再更新到最新版;在整个更新路径中,断点可以有多个,在开发者角度来看,每一次设置断点都意味着删除了之前的更新钩子,从用户角度看,如果在当前版本后续出现了多个断点,那么在更新时不能够一次性更新到最新版,而需要下载后续发布的多个版本(每个断点号需要一个版本),从低到高逐个进行更新。</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">那么如何设置断点呢?</span>Drupal<span style="font-family:宋体">使用以下断点设置钩子:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">hook_update_last_removed()</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该钩子应放在</span>mymodule.install<span style="font-family:宋体">中,没有参数,返回值即为断点号,是被删除的最大那个模式更新钩子的</span>N<span style="font-family:宋体">值,比如该钩子返回“</span>8500<span style="font-family:宋体">”,即表示在本版软件中,模式号小于或等于</span>8500<span style="font-family:宋体">的钩子已经被删除了,仅包含大于</span>8500<span style="font-family:宋体">的更新钩子,如果用户想升级到本版软件,那么需要先升级到携带</span>8500<span style="font-family:宋体">号钩子的某个版本才行(由于新版本不一定会实现模式更新钩子,因此这样的版本可能不止一个,更新到任意一个均可),通常</span>Drupal<span style="font-family:宋体">会在主版本更新时设置断点。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">断点的检查在</span>system_requirements($phase)<span style="font-family:宋体">钩子中进行(</span>$phase<span style="font-family:宋体">为</span>update<span style="font-family:宋体">),如果用户系统模式号小于断点号,则无法执行更新,必须大于或等于。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk44407170"><b><span style="font-family:宋体">模式更新依赖钩子</span></b></a><b><span style="font-family:宋体">:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">钩子函数签名:</span>hook_update_dependencies()</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">派发位置:</span>update_retrieve_dependencies()<span style="font-family:宋体">,放置在</span>mymodule.install<span style="font-family:宋体">文件中</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">同一个模块声明的模式更新钩子(</span>hook_update_N(&amp;$sandbox) <span style="font-family:宋体">)是按</span>N<span style="font-family:宋体">(更新序号或模式号)从小到大执行的,那不同模块声明的模式更新钩子谁先执行呢?模块间对此可能有先后需求,比如一个模块声明的模式更新钩子必须在另一个模块声明的某序号的模式更新钩子之后运行,换句话说模块间声明的模式更新钩子可能有依赖关系,因此系统派发该钩子来收集这种依赖关系,其决定着模块间模式更新钩子的执行顺序,返回值是一个多维数组,类似如下:</span></span></span></p> <p style="text-align:justify; text-indent:21pt">&nbsp;</p> <pre> <code class="language-php"> $dependencies['mymodule'][8001] = [ 'another_module' =&gt; 8003, ]; </code></pre> <p style="text-indent:21.2pt; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该示例指示</span>mymodule<span style="font-family:宋体">模块提供的:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">mymodule_update_8001()</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">需要在</span>another_module<span style="font-family:宋体">模块提供的:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">another_module_update_8003()</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">之后运行,如果</span>mymodule_update_8001()<span style="font-family:宋体">依赖其他多个模块提供的更新钩子,只需要在值数组中添加多个声明即可,如果依赖于同一个模块提供的多个模式更新钩子,只需声明</span>N<span style="font-family:宋体">值最高的那个即可,因为相同模块的模式更新钩子总是按</span>N<span style="font-family:宋体">值从小到大的顺序执行。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">更新声明也可以反过来声明,如下:</span></span></span></p> <p style="text-align:justify; text-indent:21pt">&nbsp;</p> <pre> <code class="language-php"> $dependencies[' another_module'][8003] = [ ' mymodule' =&gt; 8001, ]; </code></pre> <p style="text-indent:21.2pt; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这指示</span>mymodule_update_8001()<span style="font-family:宋体">需要在</span>another_module_update_8003()<span style="font-family:宋体">之前执行,但这样的声明很罕见,因为如果后者已经被执行,那么该声明将被忽略。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">数据更新钩子:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">钩子函数签名为:</span><a name="_Hlk43455284">hook_post_update_</a>NAME(&amp;$sandbox)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">和模式更新钩子(</span>hook_update_N()<span style="font-family:宋体">)不同的是该钩子专门用于更新数据,放置在模块根目录的以下文件中:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">“</span>MODULE.post_update.php<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">其中</span>NAME<span style="font-family:宋体">可以是任意有效的机器名,称为数据更新标识符,模块内该钩子按</span>NAME<span style="font-family:宋体">的字母序执行(通过</span>PHP<span style="font-family:宋体">函数</span>sort<span style="font-family:宋体">排序),因此如果有强制顺序要求,那么应该采用合适的</span>NAME<span style="font-family:宋体">,建议采用数字前缀或完全采用数字;模块间数据更新钩子的执行顺序由模块机器名决定,这一点和模式更新钩子不同,不存在依赖机制,没有对应的依赖收集钩子。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">每个数据更新钩子只会被系统执行一次,执行过的数据更新钩子函数名被保存在以下位置:</span></span></span></p> <pre> <code class="language-php">\Drupal::keyValue('post_update')-&gt;get('existing_updates', [])</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">数据更新钩子也是在批处理流程中执行,但不直接作为批处理回调,而是被以下批处理回调执行:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">update_invoke_post_update($function, &amp;$context)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数含义如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">$function<span style="font-family:宋体">:要被调用执行的数据更新钩子函数名</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">$context<span style="font-family:宋体">:批处理上下文参数</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">和模式更新钩子一样,在数据更新钩子函数中,参数</span>$sandbox<span style="font-family:宋体">并不被传递完整的批处理上下文参数</span>$context<span style="font-family:宋体">,而是</span>$context['sandbox']<span style="font-family:宋体">,钩子内部必须以引用接收,如果钩子在一个请求中无法完成任务,需要多请求多次执行,那么可以设置参数项</span>$sandbox ['#finished']<span style="font-family:宋体">,这将被系统传递给批处理上下文参数的</span>$context['finished']<span style="font-family:宋体">,值为</span>0-1<span style="font-family:宋体">之间的浮点数,表示进度,详见批处理</span>API<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">系统在全部模式更新钩子函数成功执行完后才开始执行数据更新钩子,如任一模式更新钩子发生错误(抛出异常)则不会被执行,开始执行前系统会失效全部缓存。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当数据更新钩子执行成功时,应返回一个字符串(或翻译对象),这将在更新脚本运行完成后显示给用户,如果不需要显示,也可返回</span>NULL<span style="font-family:宋体">或不返回;执行成功时系统会将其函数名记录起来以避免再次执行,记录位置见前文。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当数据更新钩子执行遇到异常时,应抛出以下异常:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">Drupal\Core\Utility\UpdateException</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如:</span></span></span></p> <p style="text-align:justify; text-indent:21pt">&nbsp;</p> <pre> <code class="language-php">use Drupal\Core\Utility\UpdateException; throw new UpdateException('Description of what went wrong'); </code></pre> <p style="text-indent:21.2pt; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">系统收到异常后会记录日志,并将本数据更新钩子视为执行不成功,一旦被视作不成功,那么后续的数据更新钩子将不会被执行,且不会将其记录为已执行,管理员应查看日志并手动排查,然后再次执行更新。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">数据更新钩子的移除:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">前文讲到系统可以通过“更新断点”来移除早期的模式更新钩子,与之对应的,数据更新钩子也可以移除,但不是通过“更新断点”,数据更新钩子的移除没有断点概念,取而代之的是模块可实现以下钩子:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">hook_removed_post_updates()</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该钩子放在模块根目录的</span>MODULE.post_update.php<span style="font-family:宋体">文件中,用于指明从第一发布版本开始历次被移除的数据更新钩子</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回一个数组,键名是该模块已从代码中删除的数据更新钩子函数名(包含从第一个版本开始的全部),键值为第一个删除了该数据更新钩子的模块版本号(用于提示没有执行过这些钩子的用户需要先下载哪个版本进行更新)。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果没有删除过或没有过数据更新钩子,那么该钩子可以返回空数组或不实现;一旦有删除过,那么就必须在该钩子中声明(包含从首版开始的所有删除),系统以此追踪是否会有数据更新钩子被漏执行,逻辑上讲被删除的数据更新钩子理应是曾经被执行过的,其会存在于以下记录中:</span></span></span></p> <pre> <code class="language-php">\Drupal::keyValue('post_update')-&gt;get('existing_updates', [])</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果记录中没有,那么说明没有被执行过,但又被移除的话将导致漏执行,当系统以此推断将可能出现漏执行时,则不允许继续执行更新,该判断工作在以下钩子中进行:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">system_requirements($phase)</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数</span>$phase<span style="font-family:宋体">为</span>update<span style="font-family:宋体">,即更新脚本的需求检查阶段</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注:从代码中删除的数据更新钩子必须被申明在</span>hook_removed_post_updates()<span style="font-family:宋体">钩子中,反过来,一旦申明就必须删除,否则系统将抛出异常。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">更新</span>update</b><b><span style="font-family:宋体">模块:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">核心提供了“</span>update<span style="font-family:宋体">”模块,用来为维护人员提供更新辅助,如自动检查新版本、通知用户有更新、下载等;该模块属于辅助性质,让维护更加方便,本篇仅面向开发者介绍更新原理,因此关于该模块不做过多介绍。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">补充:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、更新相关官网文档:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">更新用户向导:</span></span></span></p> <p style="text-indent:0cm; text-align:justify">https://www.drupal.org/docs/updating-drupal</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">更新概述:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">https://www.drupal.org/docs/updating-drupal/overview-of-options</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">更新</span>api<span style="font-family:宋体">文档:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">https://www.drupal.org/docs/drupal-apis/update-api</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模式更新钩子文档:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Extension%21module.api.php/function/hook_update_N/9.0.x</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、只要有更新被执行(准确的说是更新流程运行到批处理阶段),那么系统将失效全部缓存,这在访问量巨大的站点需要注意缓存雪崩问题</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、如果更新能运行到批处理阶段,须注意:由于批处理前会自动将系统设置成维护模式,如果批处理过程由于异常中断,那么管理员需要检查是否上线站点</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> </div> <section data-drupal-selector="comments" class="comments"> <h2 class="comments__title">反馈互动</h2> <div class="add-comment"> <div class="add-comment__form"> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=210&amp;2=comment&amp;3=comment" token="0HQdQkVEnVheppCUg1c4U7pZW02UeIOYJL7M5jL8_Dw"></drupal-render-placeholder> </div> </div> </section> Fri, 24 Jul 2020 00:52:40 +0000 云客 210 at http://indrupal.com http://indrupal.com/node/210#comments 150. 接口翻译导入导出与删除 http://indrupal.com/node/209 <span>150. 接口翻译导入导出与删除</span> <span><span>yunke</span></span> <span><time datetime="2020-07-10T00:43:01+08:00" title="2020-07-10 00:43 星期五">周五, 07/10/2020 - 00:43</time> </span> <div class="text-content clearfix field field--name-body field--type-text-with-summary field--label-hidden field__item"><p><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">本篇讲述接口(界面)翻译(</span>Interface Translation<span style="font-family:宋体">)数据的相关处理,也就是通常所讲的</span>UI<span style="font-family:宋体">文本翻译,不包含内容、配置翻译等,关于系统翻译原理请见本系列翻译系统篇。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b>PO</b><b><span style="font-family:宋体">文件:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">即“</span>.po<span style="font-family:宋体">”扩展名的文件,是可移植对象“</span>Portable Object<span style="font-family:宋体">”的缩写,用于解决文本国际化(翻译)问题,换句话说,即用来储存翻译数据,在很多国际化软件中都能见其身影,源于</span>GNU<span style="font-family:宋体">计划的</span>gettext<span style="font-family:宋体">项目,见:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">http://www.gnu.org/software/gettext/</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">使用文档页:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">http://www.gnu.org/software/gettext/manual/html_node/index.html</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">“</span>PO<span style="font-family:宋体">文件”的内容包含了翻译源字符串、目标字符串、上下文、单复数等信息,格式见以下文档:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">&nbsp;&nbsp;http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在</span>Drupal<span style="font-family:宋体">中即用</span>po<span style="font-family:宋体">文件来导入、导出翻译,一个</span>po<span style="font-family:宋体">文件储存一种语言翻译</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">翻译数据的获取和建立:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">获取翻译:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">Drupal<span style="font-family:宋体">用户通常从官方翻译服务器下载</span>po<span style="font-family:宋体">文件,地址为:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://localize.drupal.org/</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在该服务器可以得到</span>drupal<span style="font-family:宋体">安装版(包含了所有核心模块)的翻译:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">点击“</span>Downloads<span style="font-family:宋体">”后</span><span style="font-family:宋体">找到所需语言,以及系统版本,点击即可下载</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">还可以得到社区贡献模块的翻译:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">点击“</span>Downloads<span style="font-family:宋体">”后在顶部的“</span>Pick a project<span style="font-family:宋体">”处输入模块名(或输入一部分再根据系统提示进行选择),然后点击“</span>Show downloads<span style="font-family:宋体">”,即会显示相关语言和版本供用户点击下载</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">也可在首页右上角的“</span>And/or pick a project<span style="font-family:宋体">”</span> <span style="font-family:宋体">处输入模块名进入翻译页面,然后点击“</span>Export<span style="font-family:宋体">”进行更详细的下载控制</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">得到的文件名格式通常如下:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">{project}-{version}.{langcode}.po</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">由项目名、版本号、语言代码组成,比如:</span>drupal-9.0.0.zh-hans.po<span style="font-family:宋体">,下载</span>po<span style="font-family:宋体">文件后导入站点即可(见下文)。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这里需要解释一下项目名</span>project<span style="font-family:宋体">的含义:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">项目名指示模块或主题所属的项目,在其</span>info<span style="font-family:宋体">文件的“</span>project<span style="font-family:宋体">”根键中指定,多个模块可以同属于一个项目,只需要她们</span>info<span style="font-family:宋体">文件的</span>project<span style="font-family:宋体">项有相同值即可,典型的有“</span>examples<span style="font-family:宋体">”模块、“</span>commerce<span style="font-family:宋体">”模块,这两模块有许多子模块,这些子模块和主模块同属于一个项目;核心所有模块同属于一个项目,项目名为“</span>drupal<span style="font-family:宋体">”;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在</span>info<span style="font-family:宋体">文件中“</span>project<span style="font-family:宋体">”根键是可选的,默认为空字符串,如果是放置在官网的贡献模块,在没有设置该项的情况下,会被打包器自动设置为模块机器名,翻译和系统更新均是以项目为单位进行处理的</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">建立翻译:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">一般建立翻译的人员有用户自己、翻译贡献者、模块作者;用户自己可以在后台以下地址自行翻译:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">/admin/config/regional/translate</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这在校正翻译和补充翻译时很有用</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">翻译贡献者和模块作者可以到官方翻译服务器针对安装版或具体模块贡献翻译,模块作者也可以随模块一起提供翻译,详见下文。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">翻译源字符提取:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">以上翻译服务器中的翻译源字符串是从程序代码中直接提取的,上传到官网的贡献模块均会被自动提取,开发者需要注意:提取是通过正则表达式对程序文件进行静态特征分析进行的,即查找</span>t()<span style="font-family:宋体">和</span>format_plural()<span style="font-family:宋体">函数并进行正则参数解析,系统并没有更智能更复杂的提取处理,如果开发者在这些函数的源字符串参数中传递变量而不是字面量,那么将导致无法提取,因此建议开发者尽量避免这样做,无法提取将导致无法在翻译服务器上翻译,但在系统后台依然可以进行翻译。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">管理界面导入、导出:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">导入地址:</span>/admin/config/regional/translate/import</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">导出地址:</span>/admin/config/regional/translate/export</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">用户自己在系统后台进行的翻译往往不想被下载的翻译覆写,因此需要更高的优先级,所以系统可将用户自己提供的翻译数据(文件),标记为“自定义”翻译,对应的,如果翻译数据(文件)来自官方社区(翻译服务器</span>localize.drupal.org<span style="font-family:宋体">)或模块自带,那么这种翻译称为“非自定义”翻译,翻译数据的这种属性被系统保存在数据库表“</span>locales_target<span style="font-family:宋体">”的“</span>customized<span style="font-family:宋体">”中,这样就可以在导入翻译时,给用户一个是否覆写自己提供的翻译的选择。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">在导入翻译时:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">翻译文件本身并不表明自己是否为自定义,用户可以指定以下选项:</span></span></span></p> <p style="text-indent:14.15pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">是否将导入的翻译当做自定义翻译</span></span></span></p> <p style="text-indent:14.15pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">是否覆写非自定义翻译</span></span></span></p> <p style="text-indent:14.15pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">是否覆写存在的自定义翻译</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在导入时,如果指定的语言和</span>po<span style="font-family:宋体">文件中真实的翻译语言不一致时,将以指定的语言为准</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">在导出翻译时:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">导出的翻译也并不包含是否为自定义,可以指定以下选项:</span></span></span></p> <p style="text-indent:14.15pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">是否导出非自定义翻译</span></span></span></p> <p style="text-indent:14.15pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">是否导出自定义翻译</span></span></span></p> <p style="text-indent:14.15pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">是否包含无翻译的源文本</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">新装模块后的翻译处理:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在模块被安装后系统会自动为其导入翻译吗?在同时满足以下</span>4<span style="font-family:宋体">个条件时将会导入:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">1<span style="font-family:宋体">、以下配置项被打开(默认是打开的,模块可修改),即值为</span>true<span style="font-family:宋体">:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">\Drupal::config('locale.settings')-&gt;get('translation.import_enabled')</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">2<span style="font-family:宋体">、系统中有可翻译的语言,即是多语言网站或英语被设置为可翻译的</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">3<span style="font-family:宋体">、系统不处于初始安装阶段,即实例化站点阶段(安装</span>drupal<span style="font-family:宋体">阶段)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">4<span style="font-family:宋体">、被安装模块建立了新的项目时,具体讲即被安装模块申明了项目且项目名和模块机器名相同,这样的模块通常是项目的主模块,反过来讲一个项目仅在主模块被安装时才执行导入,子模块不会执行,翻译是以项目为单位进行导入和更新的,并不以模块为单位,翻译文件应包含项目中所有模块的翻译数据。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">前文已讲到声明项目是通过</span>info<span style="font-family:宋体">文件的“</span>project<span style="font-family:宋体">”根键指定,位于官网的贡献模块在没有指定该项时会自动添加,值指定为模块机器名,项目名也用于系统更新,接口翻译模块允许我们不通过</span>project<span style="font-family:宋体">声明项目,而通过以下键为翻译功能专门指定项目名:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><a name="_Hlk42787794"><i>interface translation project</i></a><i><span style="font-family:宋体">:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">该项的值必须和模块机器名相同才会执行导入,一旦设置,将覆写“</span>project<span style="font-family:宋体">”项申明的项目名,而不论“</span>project<span style="font-family:宋体">”是否被设置或值如何,该项的意义在于为翻译导入功能模拟项目名,让我们在不真正申明项目名的情况下,模块安装后依然可以导入翻译。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">那么翻译数据从哪里来呢?可以远程服务器或本地文件,这在</span>info<span style="font-family:宋体">文件中设置以下键指定:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><i>interface translation server pattern</i><i><span style="font-family:宋体">:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">该项的值是获取翻译</span>po<span style="font-family:宋体">文件的地址,可以是一个网址、本地的相对或绝对地址、或者流包装器地址,为了更灵活,地址中可以使用以下占位符:</span></span></span></p> <pre> <code>"%core":表示核心版本,如“8.x” "%project":项目名 "%version":当前项目版本号,如: "8.1", "8.x-1.0",即模块声明的版本号 "%language":语言代码,如“zh-hans” </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">该项是可选的,默认值储存在以下配置中:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">\Drupal::config('locale.settings')-&gt;get('translation.default_server_pattern');</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">模块可以修改该配置,无值时将采用以下值:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">'https://ftp.drupal.org/files/translations/%core/%project/%project-%version.%language.po';</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">当地址是远程地址时,系统会自动进行下载,并保存到流包装器“</span>translations://<span style="font-family:宋体">”中,该流包装器的路径可以在以下管理后台配置</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">“</span>/admin/config/media/file-system<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">默认为:</span>sites/default/files/translations</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">如果是一个本地地址,通常会采用流包装器“</span>translations://<span style="font-family:宋体">”指定,当然也可以采用绝对或相对地址,或其他流包装器</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">如果是私用的自定义模块,因为我们知道</span>PO<span style="font-family:宋体">文件的精确位置,因此可以采用任意地址,但如果是贡献模块,且想在模块文件夹中一起提供</span>po<span style="font-family:宋体">文件,此时本地地址如何指定呢?由于模块有多个可能的安装位置,我们并不知道会被放置到何处,系统目前没有提供指示模块根目录的占位符,因此无法指定一个准确的本地地址,此时可以通过“</span>hook_install()<span style="font-family:宋体">”钩子将模块目录中的</span>po<span style="font-family:宋体">文件复制到“</span>translations://<span style="font-family:宋体">”中,然后指定本地文件位置为翻译流包装器地址即可,如“</span>translations://%project-%version.%language.po<span style="font-family:宋体">”,为了清晰的说明这个过程,这里提供一个示例:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">模块安装后自动导入附带翻译示例:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">假设模块名为“</span>yunke<span style="font-family:宋体">”,在模块的“</span>translations<span style="font-family:宋体">”根目录中提供模块各语言的翻译</span>po<span style="font-family:宋体">文件,文件名格式如下(各部分以点号做连接):</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">“</span>%project.%language.po<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">模块</span>info<span style="font-family:宋体">文件内容如下:</span></span></span></p> <pre> <code class="language-yaml">name: yunke type: module description: '模块被安装后自动导入翻译示例' version: '1.0.0' package: yunke 'interface translation project': yunke 'interface translation server pattern': 'translations://%project.%language.po' core_version_requirement: '~8.8 || ^9' datestamp: 1591272046 </code></pre> <p style="text-align:justify; text-indent:21pt">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">然后在根目录中建立“</span>yunke.install<span style="font-family:宋体">”文件,里面放入以下两个文件:</span></span></span></p> <pre> <code class="language-php"> /** * 将模块目录下的翻译文件复制到翻译流包装器目录中 * * @param $moduleName * 模块名 */ function _copy_translations($moduleName) { $directory = '/translations/'; $filename_mask = '/' . preg_quote('.po') . '$/'; $moduleHandler = \Drupal::moduleHandler(); if (!$moduleHandler-&gt;moduleExists('locale') || !$moduleHandler-&gt;moduleExists($moduleName)) { return; } $langcodes = array_keys(locale_translatable_language_list()); if (empty($langcodes)) { return; } $translationsDirectory = $moduleHandler-&gt;getModule($moduleName)-&gt;getPath() . $directory; if (!is_dir($translationsDirectory)) { return; } $files = \Drupal::service('file_system')-&gt;scanDirectory($translationsDirectory, $filename_mask, ['key' =&gt; 'uri', 'recurse' =&gt; FALSE]); if (empty($files)) { return; } $translations = []; foreach ($files as $uri =&gt; $file) { $name = explode('.', $file-&gt;name); $langcode = end($name); if (in_array($langcode, $langcodes)) { $translations[$file-&gt;filename] = $uri; } } foreach ($translations as $filename =&gt; $uri) { \Drupal::service('file_system')-&gt;copy($uri, 'translations://' . $filename, \Drupal\Core\File\FileSystemInterface::EXISTS_REPLACE); } } /** * Implements hook_install(). */ function yunke_install() { $moduleName = substr(__FUNCTION__, 0, -8); _copy_translations($moduleName); return; } </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">说明:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">以上示例中,</span>info<span style="font-family:宋体">文件内容向系统说明了需要导入翻译,并指定了翻译文件的位置和文件名格式,在安装钩子中实时从模块目录将翻译文件复制到</span>info<span style="font-family:宋体">文件指定的位置,这样模块安装后就能自动导入模块提供的翻译文件了(具体在随后的</span>locale_modules_installed($modules)<span style="font-family:宋体">钩子中进行导入),当模块卸载时,也会自动删除翻译流包装器中的</span>po<span style="font-family:宋体">文件</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">被导入的翻译被视为非自定义的,是否会覆写存在的自定义或非自定义翻译呢?这取决于以下配置对象:</span></span></span></p> <pre> <code class="language-php">\Drupal::config('locale.settings')-&gt;get('translation.overwrite_not_customized'); \Drupal::config('locale.settings')-&gt;get('translation.overwrite_customized'); </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">以上是系统提供的自动导入方法,通常也是我们最优先使用的方法,但在有些情况下我们需要以程序方式自行导入翻译,这怎么处理呢?(见下文)。</span> </span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">程序方式导入翻译:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">前文讲述了系统提供的在模块安装后自动导入翻译的功能,该功能仅用于模块安装后的自动导入,这里我们采用自行实现,该实现可以用在任意地方,示例同样以模块安装后自动导入翻译为目标。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">翻译的导入需要产生很多数据库连接,过程可能很漫长,为了避免</span>php<span style="font-family:宋体">超时,一般会采用批处理方式进行,因此我们以批处理方式实现(如果需要在一个请求中完成可以将批处理修改为非渐进式即可)。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">程序方式导入翻译示例:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">为了模块安装后自动导入翻译,我们同样需要实现以下钩子:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">&nbsp;&nbsp;hook_install()</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">该钩子放置在模块根目录的“</span>module.install<span style="font-family:宋体">”文件中,其被调用时,模块已经完全安装完成,同样假设被安装的模块机器名是“</span>yunke<span style="font-family:宋体">”,翻译文件放置在模块根目录的“</span>translations<span style="font-family:宋体">”文件夹中,文件名格式为“</span>$langcode.po<span style="font-family:宋体">”,如“</span>zh-hans.po<span style="font-family:宋体">”,此时钩子函数如下:</span></span></span><br /> &nbsp;</p> <pre> <code class="language-php"> /** * Implements hook_install(). */ function yunke_install() { $filename = '/' . preg_quote('.po') . '$/'; //搜寻翻译文件的正则表达式 翻译文件为“$langcode.po” $directory = '/translations/'; //翻译文件所在的模块根目录 $module = substr(__FUNCTION__, 0, -8); //要导入翻译的模块 $moduleHandler = \Drupal::moduleHandler(); if (!$moduleHandler-&gt;moduleExists('locale')) { return; } $langcodes = array_keys(locale_translatable_language_list()); if (empty($langcodes)) { return; } $translationsDirectory = $moduleHandler-&gt;getModule($module)-&gt;getPath() . $directory; if (!is_dir($translationsDirectory)) { return; } $files = \Drupal::service('file_system')-&gt;scanDirectory($translationsDirectory, $filename, ['key' =&gt; 'name', 'recurse' =&gt; FALSE]); if (empty($files)) { return; } $translations = array_intersect($langcodes, array_keys($files)); //找出将导入的翻译文件 $options = [ 'langcode' =&gt; NULL, 'overwrite_options' =&gt; [ 'not_customized' =&gt; true, 'customized' =&gt; true, ], 'customized' =&gt; LOCALE_NOT_CUSTOMIZED, 'finish_feedback' =&gt; false, 'use_remote' =&gt; false, ]; $moduleHandler-&gt;loadInclude('locale', 'bulk.inc'); $operations = []; foreach ($translations as $langcode) { $options['langcode'] = $langcode; $file = $files[$langcode]; $file-&gt;project = 'drupal'; $file-&gt;version = \Drupal::VERSION; $file-&gt;langcode = $langcode; $operations[] = ['locale_translate_batch_import', [$file, $options]]; } $operations[] = ['locale_translate_batch_import_save', []]; $operations[] = ['locale_translate_batch_refresh', []]; $batch = [ 'operations' =&gt; $operations, 'title' =&gt; t('Importing interface translations'), 'progress_message' =&gt; '', 'error_message' =&gt; t('Error importing interface translations'), 'file' =&gt; $moduleHandler-&gt;getModule('locale')-&gt;getPath() . '/locale.bulk.inc', ]; if ($options['finish_feedback']) { $batch['finished'] = 'locale_translate_batch_finished'; } batch_set($batch); unset($options['langcode']); if ($batch = locale_config_batch_update_components($options, $translations)) { batch_set($batch); } } </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">说明:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">以上实现在模块安装后,会判断系统中存在哪些可翻译的语言,如果没有可翻译语言则什么也不做,如果有就会扫描翻译目录中是否提供了对应语言(以文件名为依据),当有提供时就会进行导入。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">制作</span>PO</b><b><span style="font-family:宋体">文件:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">为了避免模块提供的</span>po<span style="font-family:宋体">文件被直接下载,通常需要放置一个拒绝客户端访问的“</span>.htaccess<span style="font-family:宋体">”文件在翻译目录中,你可以从配置同步目录中复制。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">po<span style="font-family:宋体">文件需要头部,内容也有格式要求,为了便于读者快速制作,这里也提供一个内容范例:</span></span></span></p> <pre> <code class="language-yaml"># Chinese, Simplified translation # msgid "" msgstr "" "Language-Team: Chinese, Simplified\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" msgid "yunke is from sichuan" msgstr "云客来自四川" # 示范变量 msgid "%name: may not be longer than @max characters." msgstr "%name:不能长于@max个字符。" # 示范双引号转义 msgid "\"On\" label" msgstr "“开”标签" # 示范复数形式 msgid "1 minute" msgid_plural "@count minutes" msgstr[0] "1 分钟" msgstr[1] "@count 分钟" # 示范上下文 msgctxt "Long month name" msgid "May" msgstr "五月" # 更多格式请参考http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files # 或者使用专用的Gettext编辑器 </code></pre> <p>&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">翻译数据清理:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">系统在储存翻译数据时,并不记录源字符串来自哪个模块,如果多个模块提供了相同的源字符串,那么只会被记录一条,换句话说翻译是被所有模块共享,因为这个原因,模块在卸载时不能去清理自己提供的翻译,如果强行清理那么可能导致共享这些翻译的模块丢失翻译数据;同样的原因,系统也没有别的办法去清理翻译数据,因此系统没有提供翻译删除功能。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">这就导致了一个问题:可能再也不会用到的翻译将一直驻留在系统中,时间流逝,不断有模块安装卸载,翻译数据会越来越多,垃圾数据也越来越多,因此强烈建议读者不要到生产站点去试安装模块,尤其是质量、来路存疑的模块。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">如果真有一天无用翻译增长到无法忍受了怎么办?可以克隆站点,在克隆系统上删除全部翻译相关数据库表,再重新翻译全站后导出翻译文件,然后删除生产站点上全部翻译相关数据库表,最后导入之前导出的翻译文件。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">思考:为什么不记录提供翻译源字符串的是哪个模块呢?因为翻译数据量很大,很常用,这样做将极大浪费性能</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">项目的翻译工作流设计:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在非英语国家,往往开发人员并不是最好的翻译人员,由于英语水平有限,通常需要借助翻译人员的帮助,这就导致了一个问题,在做开发时怎么进行翻译工作呢?这里以中文来做探讨,这不会失去一般性,假设开发者的英文书写水平很差,又难以提高英语水平:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">如果每写一个</span>T<span style="font-family:宋体">函数都去询问一下英语怎么表达,几乎是不可能做到的,即便是好几个文件写完再去问也很麻烦,读者可能会想到开发元语言可以不是英语(确实如此),是不是可以直接在</span>T<span style="font-family:宋体">函数中用自己的语言呢?这样再提供英文的</span>po<span style="font-family:宋体">文件就好了,这看起来曙光一现,但很快就会发现中文没有单复数概念,很容易用错翻译函数,如果还能勉强克服的话,当遇到有上下文信息的翻译时就可能会彻底失望了,比如中文的“可以”意思很清晰,没有歧义,但英文的“</span>may<span style="font-family:宋体">”却需要传递上下文标识;开发元语言虽然可以不是英文,但翻译函数却是为英文设计的,看来在翻译函数中直接用中文行不通,还会为将来国际化协作埋下隐患。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">推荐以下工作流程:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">在用到翻译函数的地方留下</span>todo<span style="font-family:宋体">记号,比如“</span>//@todo <span style="font-family:宋体">翻译:注释”,可暂时在函数中使用中文,等到项目最后阶段会同翻译人员一起处理这些</span>todo<span style="font-family:宋体">标记,在</span>IDE<span style="font-family:宋体">工具(比如</span>phpstorm<span style="font-family:宋体">)的帮助下很容易找到它们,协同翻译时一并制作中文的</span>po<span style="font-family:宋体">文件。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><span style="font-family:宋体">读者朋友们:除了让英语蹩脚的开发者提高英文水平外,您有更好的办法帮助他们吗?如何改进这个工作流程呢?</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif"><b><span style="font-family:宋体">补充:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">1<span style="font-family:宋体">、翻译相关官网文档地址:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Language%21language.api.php/group/i18n/8.8.x</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">https://api.drupal.org/api/drupal/core%21modules%21locale%21locale.api.php/group/interface_translation_properties/8.8.x</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">2<span style="font-family:宋体">、翻译导入后,系统默认会执行和翻译相关的更新,如刷新</span>js<span style="font-family:宋体">翻译数据、配置覆写等</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">3<span style="font-family:宋体">、模块安装后系统会自动基于项目去更新或导入翻译,因为</span>locale<span style="font-family:宋体">模块实现了</span>hook_modules_installed()<span style="font-family:宋体">,在模块卸载时会检查是否需要删除</span>po<span style="font-family:宋体">文件等,因为</span>locale<span style="font-family:宋体">模块实现了</span>hook_module_preuninstall()<span style="font-family:宋体">):</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> </div> <section data-drupal-selector="comments" class="comments"> <h2 class="comments__title">反馈互动<span class="comments__count">1</span></h2> <div class="add-comment"> <div class="add-comment__form"> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=209&amp;2=comment&amp;3=comment" token="OxMQl9MNyO5Ozx66YFYnJzt2HiZEoOsLthrf6eJoXdE"></drupal-render-placeholder> </div> </div> <article data-comment-user-id="0" id="comment-10" class="comment js-comment comment--level-1 by-anonymous" role="article" data-drupal-selector="comment"> <span class="hidden" data-comment-timestamp="1596432253"></span> <div class="comment__picture-wrapper"> <div class="comment__picture"> <div> </div> </div> </div> <div class="comment__text-wrapper"> <footer class="comment__meta"> <p class="comment__author"><span>Lychee (未验证)</span></p> <p class="comment__time">4 年 8 个月 之前</p> </footer> <div class="comment__content"> <h3><a href="/comment/10#comment-10" class="permalink" rel="bookmark" hreflang="zh-hans">棒</a></h3> <div class="text-content field field--name-comment-body field--type-text-long field--label-hidden field__item comment__text-content"><p>写的太好了</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=10&amp;1=default&amp;2=zh-hans&amp;3=" token="KCh8KPxOZpiuclhMJP302Kp-mt6Fj1tp6QPimuH4fyU"></drupal-render-placeholder> </div> </div> </article> </section> Thu, 09 Jul 2020 16:43:01 +0000 yunke 209 at http://indrupal.com http://indrupal.com/node/209#comments 149.模块安装与卸载过程 http://indrupal.com/node/208 <span>149.模块安装与卸载过程</span> <span><span>云客</span></span> <span><time datetime="2020-06-19T08:15:14+08:00" title="2020-06-19 08:15 星期五">周五, 06/19/2020 - 08:15</time> </span> <div class="text-content clearfix field field--name-body field--type-text-with-summary field--label-hidden field__item"><p><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">为了介绍模块的安装与卸载过程,本文会先做一些相关知识的介绍</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块</span>info</b><b><span style="font-family:宋体">文件键名:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在模块的“</span>module.info.yml<span style="font-family:宋体">”文件中,各键名解释如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">name</span></span><span style="font-family:宋体">:必选,模块人类可读名称</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">description</span></span><span style="font-family:宋体">:可选,模块描述,默认为空字符</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">type</span></span><span style="font-family:宋体">:必选,指示本扩展类型,对于模块而言,值固定为“</span>module<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">core</span></span><span style="font-family:宋体">:在</span>D8<span style="font-family:宋体">生命周期中,模块用来指示核心兼容性,即其所需的核心版本,值须符合正则“</span>/^\d\.x$/<span style="font-family:宋体">”,通常设置为“</span>8.x<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">core_version_requirement</span></span><span style="font-family:宋体">:在</span>D9<span style="font-family:宋体">生命周期中模块不再使用“</span>core<span style="font-family:宋体">”项指示核心兼容性,而用该项进行精确详细的语义化版本约束,其值参考</span>composer<span style="font-family:宋体">版本约束值(在内部使用</span>composer<span style="font-family:宋体">组件进行版本约束计算),但该选项早在</span>8.7.7<span style="font-family:宋体">开始支持,因此在过渡阶段(</span>8.7.7<span style="font-family:宋体">之后的</span>D8<span style="font-family:宋体">版本)和</span>core<span style="font-family:宋体">选项可二选一存在;通常设置为“</span>~8.8 || ^9<span style="font-family:宋体">”,表示模块可用于</span>V8.8.*<span style="font-family:宋体">到</span>V9<span style="font-family:宋体">的所有版本以及所有</span>D9<span style="font-family:宋体">版本(因为他们</span>API<span style="font-family:宋体">相同);如果模块适用于全部</span>D8<span style="font-family:宋体">和</span>D9<span style="font-family:宋体">版本那么可以设置为“</span>^8 || ^9<span style="font-family:宋体">”,但须同时设置“</span>core: 8.x<span style="font-family:宋体">”,这样的模块很少,因为</span>D8<span style="font-family:宋体">中有很多弃用代码被</span>D9<span style="font-family:宋体">移除了,为了放下历史包袱通常模块也会随之移除。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">php</span></span><span style="font-family:宋体">:可选,指示模块运行所需的最低</span>php<span style="font-family:宋体">版本号,默认为常量</span>DRUPAL_MINIMUM_PHP<span style="font-family:宋体">的值,即核心系统允许的最小</span>PHP<span style="font-family:宋体">版本号。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">core_incompatible</span></span><span style="font-family:宋体">:内部使用,指定无效,布尔值,在内部自动依据前面三个选项计算设置,指示与核心是否不兼容</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">package</span></span><span style="font-family:宋体">:可选,默认为“</span>Other<span style="font-family:宋体">”模块所属的包名,相当于模块分组的组名,在安装页面将按组显示模块</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk42787845"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">project</span></span></a><span style="font-family:宋体">:可选,默认值为空字符串,申明模块所属的项目名称,多个模块可属于同一个项目,所有核心模块均属于“</span>drupal<span style="font-family:宋体">”项目,如果是放置在官网的贡献模块,在没有设置该项的情况下,会被打包器自动设置为模块机器名,翻译和系统更新均是以项目为基础进行处理</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">dependencies</span></span><span style="font-family:宋体">:可选,指示模块需要的依赖,值为依赖字符串构成的数组,依赖字符串可有三种形式“</span>module<span style="font-family:宋体">”、“</span>project:module<span style="font-family:宋体">”、“</span>project:module (&gt;=version, &lt;=version)<span style="font-family:宋体">”,括号中为版本约束</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">version</span></span><span style="font-family:宋体">:可选,默认为空字符,指示版本号,或使用“</span>VERSION<span style="font-family:宋体">”,表示与核心使用相同版本号</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">hidden</span></span><span style="font-family:宋体">:可选,布尔值,当为</span>true<span style="font-family:宋体">时,在模块安装页面将隐藏模块,默认为</span>false</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">configure</span></span><span style="font-family:宋体">:可选,模块的配置主页路由名</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">configure_parameters</span></span><span style="font-family:宋体">:可选,仅在</span>configure<span style="font-family:宋体">存在时有效,值为路由所需的路由参数</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">required</span></span><span style="font-family:宋体">:</span></span><span style="font-family:宋体">可选,布尔值,默认为</span></span><span lang="EN-US" style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">false</span></span><span style="font-size:10.5pt"><span style="font-family:宋体">,指示模块是否为核心所必须的,如果该项为</span></span><span lang="EN-US" style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">true</span></span><span style="font-size:10.5pt"><span style="font-family:宋体">则系统初始安装时该模块必须被安装(而不论安装配置扩展是否有指定),且不可卸载,其所依赖的所有模块也均会被自动设置该项为</span></span><span lang="EN-US" style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">true</span></span><span style="font-size:10.5pt"><span style="font-family:宋体">,核心中该项为</span></span><span lang="EN-US" style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">true</span></span><span style="font-size:10.5pt"><span style="font-family:宋体">的模块仅有三个:</span></span><span lang="EN-US" style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">system</span></span><span style="font-size:10.5pt"><span style="font-family:宋体">、</span></span><span lang="EN-US" style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">user</span></span><span style="font-size:10.5pt"><span style="font-family:宋体">、</span></span><span lang="EN-US" style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">path_alias</span></span><span style="font-size:10.5pt"><span style="font-family:宋体">;如果系统安装后放入一个该项为</span></span><span lang="EN-US" style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">true</span></span><span style="font-size:10.5pt"><span style="font-family:宋体">的模块,则该模块无法被选中安装,因此普通模块不应设置该项</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">explanation</span></span><span style="font-family:宋体">:可选,字符串值,当</span>required<span style="font-family:宋体">项为</span>true<span style="font-family:宋体">时,该项用于提供一个解释说明</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">datestamp</span></span><span style="font-family:宋体">:可选,模块发布时间戳,默认为</span>0<span style="font-family:宋体">,用于模块升级方面</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">interface translation project</span></span><span style="font-family:宋体">:可选,在界面翻译模块中用来覆写</span>project<span style="font-family:宋体">选项,详见本系列翻译相关主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">interface translation server pattern</span></span><span style="font-family:宋体">:可选,指示翻译</span>po<span style="font-family:宋体">文件的下载或本地地址,详见本系列翻译相关主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">安装配置扩展</span><a name="_Hlk37668449">Profile</a></b><b><span style="font-family:宋体">:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">关于该类型的扩展将在本系列的系统安装篇中做详细介绍,这里仅为模块安装而做简略介绍:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在</span>Drupal<span style="font-family:宋体">系统安装时(指初始的系统安装,不是模块安装),安装表单会提供存在的该类型扩展供用户选择,在安装过程中,当完成系统基本安装后会调用被选择的</span>Profile<span style="font-family:宋体">扩展执行其提供的额外安装逻辑,开发者用该类型扩展制作某一领域的发行版;</span>Profile<span style="font-family:宋体">扩展可以被视为特殊的模块,系统安装后会被当做已经安装的模块参与系统执行(能提供钩子且钩子优先级高于其他模块);模块安装时,</span>Profile<span style="font-family:宋体">扩展可以覆写模块提供的配置。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">扩展列表模块服务:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>extension.list.module</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Extension\ModuleExtensionList</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注:该服务是在</span>D8<span style="font-family:宋体">生命周期后期加入的,加入时被暂时标记为内部状态(没有提供接口定义)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于扫描发现模块类型的扩展,包含未安装的模块,得到全部模块(含未安装)可使用以下代码:</span></span></span></p> <pre> <code class="language-php">$moduleExtensionList-&gt;reset()-&gt;getList();</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">系统会为每一个扫描到的模块派发系统信息修改钩子:</span></span></span></p> <pre> <code class="language-php">hook_system_info_alter()</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数为:</span>$extension-&gt;info<span style="font-family:宋体">(</span>info<span style="font-family:宋体">文件内容)</span>, $extension<span style="font-family:宋体">(扩展对象)</span>, $type<span style="font-family:宋体">(扩展类型)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">钩子派发位置:</span></span></span></p> <pre> <code class="language-php">\Drupal\Core\Extension\ExtensionList::doList</code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">以上</span>getList()<span style="font-family:宋体">方法返回一个数组,键名为模块机器名,键值为扩展对象:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Extension\Extension</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">此扩展对象上有一些重要属性含义如下(这些属性无法用钩子</span>system_info<span style="font-family:宋体">修改):</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">requires</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模块需要的全部依赖(模块),如没有这些依赖,模块将无法运行,依赖声明在</span>info<span style="font-family:宋体">文件的</span>dependencies<span style="font-family:宋体">键下,这里的全部是指已递归的进行了包含,即包含了依赖的依赖</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">required_by</span></span><span style="font-family:宋体">:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">所有依赖于该模块的模块,同样是递归包含,即不管是直接还是间接的依赖均已包含,如没有本模块则这些模块将无法运行</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">sort</span></span><span style="font-family:宋体">:</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">是一个非正整数,最大值为</span>0<span style="font-family:宋体">,表示依赖的顺序,值越大越靠近依赖的根端;举个例子,比如</span>block_content<span style="font-family:宋体">模块依赖</span>system<span style="font-family:宋体">模块(即没有</span>system<span style="font-family:宋体">则</span>block_content<span style="font-family:宋体">无法运行,</span>system<span style="font-family:宋体">需要先存在),此时他们可能的值是:</span>system<span style="font-family:宋体">为“</span>-2<span style="font-family:宋体">”,</span>block_content<span style="font-family:宋体">为“</span>-16<span style="font-family:宋体">”</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">以上三个属性在模块处理器的“</span>buildModuleDependencies<span style="font-family:宋体">”方法中赋值,关于排序详见本系列的有向无环图主题</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">status</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">指示模块的安装状态,</span>1<span style="font-family:宋体">为已安装,</span>0<span style="font-family:宋体">为没有安装</span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk41426869"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">weight</span></span></a><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模块权重,影响模块的钩子执行顺序,来自核心扩展配置对象(</span>core.extension<span style="font-family:宋体">)中模块保存的值,注意其和属性</span>sort<span style="font-family:宋体">的区别,</span>sort<span style="font-family:宋体">表示模块依赖顺序,而</span>weight<span style="font-family:宋体">表示模块权重,代表钩子执行顺序。</span></span></span></p> <p style="text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块顺序:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">核心扩展配置对象(</span>core.extension<span style="font-family:宋体">)</span><span style="font-family:宋体">中的模块顺序是先按权重后按模块机器名进行排序的,排序函数如下:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">module_config_sort($data)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">权重数值越大越靠后,在权重相同的情况下模块机器名以字母顺序排序,“</span>a<span style="font-family:宋体">”在前,“</span>z<span style="font-family:宋体">”在后,该顺序并未考虑也不代表模块间的依赖关系,换句话说模块依赖不对顺序造成影响;此顺序影响模块钩子的执行顺序,在“</span>module_implements<span style="font-family:宋体">”修改钩子没有进行特别顺序调整的情况下,该顺序即是钩子的执行顺序,越靠后越后执行,意味着越靠后钩子函数的优先级越高(后面的修改钩子能修改前面的修改钩子的处理结果),如何修改该权重呢,调用以下函数即可:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">module_set_weight($module, $weight)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注意:设置权重时,值不要超过</span>1000<span style="font-family:宋体">,因为安装配置扩展的权重为</span>1000<span style="font-family:宋体">,须保证其最后执行。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">权限处理器服务:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id: user.permissions</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\user\PermissionHandler</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于收集所有模块定义的权限,内部通过</span>yaml<span style="font-family:宋体">发现机制扫描模块根目录下的“</span>module.permissions.yml<span style="font-family:宋体">”文件,该文件内容中第一级键名为权限的机器名,须跨模块全局唯一,其值是一个数组,各键如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">title</span></span><span style="font-family:宋体">:必选,权限标题,显示给人类看的权限名</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">description</span></span><span style="font-family:宋体">:可选,关于权限的描述</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk37335231"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">restrict access</span></span></a><span style="font-family:宋体">:可选,布尔值,指示该权限是否有安全风险,当为</span>true<span style="font-family:宋体">时,会向管理员显示一条统一的警告消息,以提醒该权限仅应授予可信的人员,默认为</span>false</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">warning</span></span><span style="font-family:宋体">:可选,当不想使用</span>restrict access<span style="font-family:宋体">选项带来的统一安全提示时,可使用该项自定义一条警告消息,尽量少用,最佳实践是用统一的安全提示,如果有必要可在描述中解释</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注意:当权限定义简单到只有一个标题时,以上数组可简化为一个标题值</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果有动态权限需求,可在</span>yaml<span style="font-family:宋体">文件中用“</span>permission_callbacks<span style="font-family:宋体">”做第一级键名,键值为一个回调数组,回调会经过控制器解析器处理,回调返回</span>yaml<span style="font-family:宋体">文件同样的内容,但其中不可再次设置回调。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果权限机器名产生冲突,即全局不唯一,那么静态定义优先级高于动态定义,如果是模块间冲突,那么视模块处理顺序而定,因此最佳实践是为权限机器名提供模块名前缀</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该服务方法解释:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function getPermissions();</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回所有模块定义的权限,键名为权限机器名,键值为权限定义</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function moduleProvidesPermissions($module_name);</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回布尔值,指示某模块是否提供有权限</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块安装与卸载列表页:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">模块安装页:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">路径如下:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">/admin/modules</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该页面路由名为:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">system.modules_list</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">采用表单控制器,由以下表单构建:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\system\Form\ModulesListForm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">模块卸载页:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">路径如下:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">/admin/modules/uninstall</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该页面路由名为:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">system.modules_uninstall</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">采用表单控制器,由以下表单构建:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\system\Form\ModulesUninstallForm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">以上只是系统提供的</span>UI<span style="font-family:宋体">接口,执行安装和卸载核心工作的是模块安装器。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块安装器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>module_installer</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Extension\ModuleInstaller</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于批量安装和卸载模块,核心方法是安装和卸载,接下来单独成节讲解</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块安装过程:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模块安装主要是在模块安装器中进行,但在此前系统会检查模块的安装需求能否得到满足,需求检查有两项工作:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、依赖检查:如对其他模块的依赖或对执行环境、版本等的依赖,这主要是依据模块的</span>info<span style="font-family:宋体">文件内容进行检查</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、复杂逻辑检查,检查函数为:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">drupal_check_module($module)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这将调用模块的“</span>requirements<span style="font-family:宋体">”钩子(该钩子详见下文),模块可通过该钩子进行复杂逻辑的检查。</span> </span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果需求得不到满足则模块不被安装,否则将调用模块安装器开始安装流程,执行方法如下:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal::service('module_installer')-&gt;install($modules , $enable_dependencies = TRUE);</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数</span>$modules<span style="font-family:宋体">是将被安装的模块机器名构成的索引数组</span> </span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数</span>$enable_dependencies<span style="font-family:宋体">是布尔值,指示是否自动安装模块依赖的模块,默认为</span>true<span style="font-family:宋体">(即默认安装),如果是在被安装模块列表中已经处理好依赖或者配置同步(配置同步可能会自动安装卸载模块,详见本系列配置同步篇)的情况下,该项应该被设置为</span>false<span style="font-family:宋体">,以避免性能损耗</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回布尔值</span>true<span style="font-family:宋体">表示安装已全部成功,部分模块安装成功或一个都不成功时,会抛出异常,抛出异常时,尚未安装的模块不再被安装,已被安装的模块实质上已经安装成功了,但“</span>modules_installed<span style="font-family:宋体">”钩子未被执行,关于该钩子见下文。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该方法的内部安装过程如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">一、核心兼容性检查</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">检查传递的所有将安装模块,如有不兼容的模块将抛出异常,安装过程终止</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">二、进行参数</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">$enable_dependencies</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">为</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">true</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">时的处理</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数</span>$enable_dependencies<span style="font-family:宋体">默认为</span>true<span style="font-family:宋体">,配置同步过程中的模块安装将传递</span>false<span style="font-family:宋体">,通过安装列表页安装将传递</span>true<span style="font-family:宋体">,此时将进行如下处理:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、检查模块文件是否存在,如有不存在则抛出错误,安装过程终止</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、排除已安装模块,如果传入的模块均已安装则返回</span>true<span style="font-family:宋体">,安装过程终止</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、向安装列表合并模块未被安装的依赖模块,这里注意扩展对象的</span>requires<span style="font-family:宋体">属性已经递归的包含了全部依赖,即已经包含了依赖的依赖,如有任一依赖不存在或不兼容,则抛出异常,安装过程终止</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、通过模块扩展的</span>sort<span style="font-family:宋体">属性排序被安装的模块,注意:</span>sort<span style="font-family:宋体">属性已反映了模块的依赖关系,排序后被依赖的根模块将先被安装</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注意:如果</span>$enable_dependencies<span style="font-family:宋体">为</span>false<span style="font-family:宋体">时,已被安装的传入模块将再次被安装</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">三、开始循环逐个安装模块</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">每一个模块的安装过程如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、检查模块机器名长度,如果超过常量</span>DRUPAL_EXTENSION_NAME_MAX_LENGTH<span style="font-family:宋体">的值(默认</span>50<span style="font-family:宋体">)将抛出异常,这意味着模块机器名的长度只能小于等于</span>50<span style="font-family:宋体">个字符</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、检查模块提供的默认配置(不包含可选配置)的有效性,无效将抛出异常,无效是指有默认配置的依赖得不到满足(仅指</span>module<span style="font-family:宋体">、</span>theme<span style="font-family:宋体">、</span>config<span style="font-family:宋体">类型的依赖),或者已经存在于活动配置中了</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、将模块名保存到核心扩展配置对象(</span>core.extension<span style="font-family:宋体">)中,保存时进行了模块排序(该顺序即是钩子执行顺序,见前文),该动作意味着在任何地方判断模块是否已经安装时结果都是已经安装</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、更新模块处理器(服务</span>id<span style="font-family:宋体">:</span>module_handler<span style="font-family:宋体">)中的模块列表并加载模块主文件(</span>$moduleName.module<span style="font-family:宋体">),这样就能让正被安装的模块参与钩子执行了</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">5<span style="font-family:宋体">、加载正被安装的模块的“</span>$moduleName.install<span style="font-family:宋体">”文件</span><span style="font-family:宋体">,其位于模块的根目录中,提供安装相关钩子</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">6<span style="font-family:宋体">、重置“</span>extension.list.module<span style="font-family:宋体">”服务,这是因为该服务内部保存了模块的安装状态,需要更新,但这也造成需要重新扫描硬盘,可能导致性能消耗,尤其是在一次性安装多个模块时。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">7<span style="font-family:宋体">、更新系统核心服务(服务</span>id<span style="font-family:宋体">:</span>kernel<span style="font-family:宋体">),因其中保存了已安装模块列表,更新后重新取回模块处理器</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">8<span style="font-family:宋体">、在容器中替换路由提供器服务为“</span>router.route_provider.lazy_builder<span style="font-family:宋体">”服务,该服务能让后续安装流程中有用到路由提供器时,实时重建路由,这样做能让正被安装的模块提供的路由生效,如果后续安装流程中没有用到路由提供器,那么路由不会重建,将在安装结束时显式重建路由。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">9<span style="font-family:宋体">、执行钩子“</span>module_preinstall<span style="font-family:宋体">”,参数为正被安装的模块的机器名,在钩子中如果有异常抛出,那么安装过程会被中断结束,正被安装的模块本身也可以实现该钩子去响应自己的安装,这是会被调用的,因为此时核心扩展配置、模块处理器、核心服务中都已经有了正被安装模块的信息,但须注意其他安装过程均尚未开始,比如配置没被安装、数据库表没被建立、实体相关内容未被处理等等,见下。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">10<span style="font-family:宋体">、如果模块通过</span>hook_schema()<span style="font-family:宋体">钩子定义了数据库表,则通过其返回值进行表创建,该钩子通常放置在模块根目录的“</span>$moduleName.install<span style="font-family:宋体">”文件中,安装函数:</span>drupal_install_schema($module)<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">11<span style="font-family:宋体">、清理全部可缓存的插件定义,注意该动作会重置“</span>config.typed<span style="font-family:宋体">”服务,这样当用到配置</span>schema<span style="font-family:宋体">时,会重新扫描配置目录中的配置</span>schema<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">12<span style="font-family:宋体">、解析模块的模式</span>schema<span style="font-family:宋体">版本号(即</span>hook_update_N()<span style="font-family:宋体">中的</span>N<span style="font-family:宋体">),这用于模块将来的更新操作,详见本系列更新相关主题。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">13<span style="font-family:宋体">、进行实体类型结构更新:如果模块提供了实体类型或者已存实体类型的实体字段,那么调用实体更新管理器进行安装或更新,详见本系列实体结构更新主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">14<span style="font-family:宋体">、安装模块的默认配置,以及安装系统中所有依赖于正被安装模块且其他依赖关系能得到满足的可选配置,这些可选配置来自于已被安装的扩展和正被安装的扩展,注意:因为容器可能已被重建,所以此时需从容器中重新获取配置安装器,并恢复之前的状态</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">15<span style="font-family:宋体">、执行正被安装模块的“</span>update_last_removed<span style="font-family:宋体">”钩子,并更新其</span>schema<span style="font-family:宋体">版本信息,重置</span>schema<span style="font-family:宋体">信息静态缓存,这用于模块将来的更新操作,详见本系列更新相关主题。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">16<span style="font-family:宋体">、注册“</span>post_update<span style="font-family:宋体">”类型的更新钩子函数,要理解这一步需要涉及到系统更新方面的知识,简要的说:假设正在被安装的模块已经发展了很久,发布了很多次更新,那么新安装时被安装的仅是其中某一个更新版本,此时需要告诉系统哪些更新函数是无需执行的,这就是此步骤的意义,详见本系列系统更新主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">17<span style="font-family:宋体">、重新注册流包装器,让模块提供的流包装器立即可用,比如</span>locale<span style="font-family:宋体">模块需要用流包装器来导入翻译。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">18<span style="font-family:宋体">、重置“主题注册”服务,让被安装模块提供的主题钩子数据被收集并可用</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">19<span style="font-family:宋体">、刷新“主题处理器</span>theme_handler<span style="font-family:宋体">”服务,因为模块可以修改主题信息</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">20<span style="font-family:宋体">、执行被安装模块的“</span>install<span style="font-family:宋体">”钩子,这样模块可以在安装后执行一些动作</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">21<span style="font-family:宋体">、模块被成功安装,进行日志记录。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">四、所有模块安装完成</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">执行以下工作:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、恢复原来的路由提供器服务</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、如果过程中路由没有被重建,那么立即进行路由重建工作,以便让新装模块提供的路由生效。有探究精神的读者可能会问:“当一起安装多个模块时,是否存在前一个模块重建路由后,导致后面安装的模块提供的路由不起作用?”答案是不会的,因为在每一个模块的安装循环中,都重建了容器,因此获取到的服务是新实例化的。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、派发“</span>modules_installed<span style="font-family:宋体">”钩子,翻译的导入就在该钩子中处理,见后</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块安装注意事项:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、在缓存方面,模块安装后,系统仅失效了插件定义缓存,其他类型的缓存并不主动去失效,比如各渲染数组的缓存依然有效,假设我们实现了一个表单验证码模块,为了添加验证码控件到表单中,那么就需要模块安装结束后,通过“</span>install<span style="font-family:宋体">”钩子主动去失效表单页面缓存。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、模块名不允许出现大写字母,比如“</span>CJKTokenizer<span style="font-family:宋体">”应该改为“</span>cjk_tokenizer<span style="font-family:宋体">”,这属于</span>Drupal<span style="font-family:宋体">规范,但该规范有强制意味,原因可参考:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery::getProviderFromNamespace</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这将导致插件机制错误</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、如果有任何一个属于某模块的配置对象存在于活动配置中,那么该模块不能被安装,换句话说,只要有以该模块名为前缀的配置对象存在,那么模块无法被安装</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、我们通常从容器中取用服务的方式有以下两种:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">直接使用:</span>\Drupal::service(' serviceName')</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">缓存到变量中:</span>$service =\Drupal::service(' serviceName')</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">由于模块安装会重建容器,那么在同一个请求处理过程中,以上两种方式是有区别的,前者将取用新构造的服务,后者将继续使用之前容器的服务。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块安装相关钩子:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">钩子名:</span></span></span><a name="_Hlk41406228"></a><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">requirements</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于模块在安装、更新时进行需求检查,也用于模块正常使用期间进行状态报告,该钩子必须放置在“</span>module_name.install<span style="font-family:宋体">”文件中。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回值是一个数组,键名是被检查项目的标识名称,该名称随意,但需要保证唯一,建议采用模块名做前缀,键值为一个数组,各键含义如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>severity</i><i><span style="font-family:宋体">:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">状态信息或需求不被满足的严重级别,值为四个常量之一:</span>REQUIREMENT_INFO<span style="font-family:宋体">(表示检查仅传递状态信息)、</span>REQUIREMENT_OK<span style="font-family:宋体">(需求成功满足)、</span>REQUIREMENT_WARNING<span style="font-family:宋体">(需求勉强满足,安装会进行但发出警告)、</span>REQUIREMENT_ERROR<span style="font-family:宋体">(错误,需求完全不能满足,终止安装);整个返回值中,只要有一个检查项目为错误级别,那么整体就被认为是错误级别。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>title</i><i><span style="font-family:宋体">:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">需求项的显示名称,一个翻译对象</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>description</i><i><span style="font-family:宋体">:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">需求或状态的描述信息,一个翻译对象</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>value</i><i><span style="font-family:宋体">:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当前值(</span>version, time, level<span style="font-family:宋体">等),在参数为安装时,仅应表示版本号,如果不适用则不要去设置该项。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该钩子仅接收一个表示场景的参数,值为</span>install<span style="font-family:宋体">、</span>update<span style="font-family:宋体">、</span>runtime<span style="font-family:宋体">之一,含义如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i><span style="font-family:宋体">参数为“</span>install</i><i><span style="font-family:宋体">”时:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:宋体">表示调用该钩子时,所属模块即将被安装,用于检查所属模块的安装需求是否能够得到满足,不满足将不被安装,注意:仅“将被安装的模块”的该钩子被调用,调用时模块尚未安装,钩子调用未必要求模块一定被安装,该钩子就是一个列子;钩子内部:不需要检查模块依赖、环境信息,这些应该通过模块的</span></span><span lang="EN-US" style="font-size:10.5pt"><span style="font-family:&quot;Calibri&quot;,sans-serif">info</span></span><span style="font-size:10.5pt"><span style="font-family:宋体">文件指定,系统会自动检查;模块的该钩子调用可能发生在系统安装时,此时配置文件还不存在,尚没有模块被安装,数据库无法使用,因此需要检查安装时机是系统建立后还是系统正在安装时(安装核心类有静态方法来判断当前是否处于系统安装时,详见本系列系统安装篇);如果需要一个类文件,那么需要直接去加载;</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i><span style="font-family:宋体">参数为“</span>update</i><i><span style="font-family:宋体">”</span> </i><i><span style="font-family:宋体">时:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">调用该钩子时,所属模块已被安装,</span>update.php<span style="font-family:宋体">正在运行,用于检查更新需求是否能被满足,</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i><span style="font-family:宋体">参数为“</span>runtime</i><i><span style="font-family:宋体">”</span> </i><i><span style="font-family:宋体">时:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">调用该钩子时,所属模块已被安装,属于运行时检查,用于进行状态报告,不局限于请求检查,比如维护任务、安全更新、站点迁移后的检查等,在(</span>/admin/reports/status<span style="font-family:宋体">)状态报告页可见其返回的信息。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">钩子名:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">module_preinstall</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于给所有模块一个机会在新模块被安装前进行响应,被安装模块本身也能实现该钩子,该钩子通常放置在主模块文件“</span>module_name.module<span style="font-family:宋体">”中</span><span style="font-family:宋体">,如果仅响应自己的安装,则可以放在“</span>module_name.install<span style="font-family:宋体">”文件中。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数:被安装的模块机器名</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">任何钩子返回值将被忽略,在钩子中抛出异常将中断模块安装过程。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在模块安装初期被派发,此时“</span>core.extension<span style="font-family:宋体">”配置中已加入了模块信息,核心和模块处理器已经重建,但数据库表(如果有的话)尚未被建立、插件缓存尚未清理、实体结构尚未更新(如果有的话)、配置尚未安装等;默认安装下没有模块实现该钩子。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">钩子名:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">install</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于让模块被安装后执行一些代码,该钩子放置在“</span>module_name.install<span style="font-family:宋体">”文件中。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在</span>D8<span style="font-family:宋体">时没有参数,在</span>D9<span style="font-family:宋体">时传入配置同步状态(布尔值),返回值被忽略,仅正在被安装的模块的该钩子被执行;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">派发该钩子时,被安装模块已经安装完成,可以像正常已安装模块一样看待。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">钩子名:</span></span></span><a name="_Hlk41377269"></a><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">modules_installed</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于模块被安装后给其他模块或自己一个机会进行响应,该钩子通常放置在主模块文件“</span>module_name.module<span style="font-family:宋体">”中,如果仅响应自己的安装,则可以放在“</span>module_name.install<span style="font-family:宋体">”文件中。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数为新安装模块机器名构成的数组,返回值被忽略,系统所有已安装模块的该钩子均被调用,包括参数中这些刚安装的模块;默认安装下有以下三个模块实现了该钩子:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>language</i><i><span style="font-family:宋体">模块</span></i><i><span style="font-family:宋体">:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">更新语言协商器,如果是</span>language<span style="font-family:宋体">模块本身被安装,那么更新视图模式和表单模式</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>locale</i><i><span style="font-family:宋体">模块</span></i><i><span style="font-family:宋体">:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">以批处理方式导入或更新翻译</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><i>node</i><i><span style="font-family:宋体">模块:</span></i></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果新装模块需要节点授权表重建,那么标记重建,详见本系列访问控制相关主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">程序上安装与卸载:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">通常我们是通过后台的模块安装管理页进行模块安装的,但有时候需要从程序层面去安装,这通常用于高级用途,比如自动化管理,那么通过程序如何去安装模块呢?主要有以下步骤:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">1</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">、解析模块依赖</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">将模块依赖的模块找出来一起安装</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">2</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">、需求检查</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">除</span>info<span style="font-family:宋体">文件中提供的需求外,还需要调用</span>requirements<span style="font-family:宋体">钩子,参见函数:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">drupal_check_module($module)</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">3</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">、调用模块安装器</span></span></span></span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal::service('module_installer')-&gt;install($modules , $enable_dependencies = TRUE);</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注意:须将翻译导入的批处理操作改为一次性处理</span></span></span></p> <p style="text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">程序上安装也参考配置同步的处理方式:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Config\ConfigImporter::processExtension</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">须注意安装后,容器被重建,有服务可能被重写,因此服务需要从容器重新获取</span></span></span></p> <p style="text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块卸载验证器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于判断一个模块是否能够被卸载,在模块安装器中被调用,实现接口如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">&nbsp;&nbsp;\Drupal\Core\Extension\ModuleUninstallValidatorInterface</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">接口中仅一个方法:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">public function validate($module);</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数为模块机器名,如果能够被卸载该方法返回空数组,如果不能被卸载,那么返回一个由字符串或翻译对象构成的数组以指明不能被卸载的原因,建议每个原因字符串不要使用任何标点符号结尾,因为它们会以列表方式显示在模块卸载页面,如果表明不能被卸载,那么在模块卸载页该模块将不能被选中卸载</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">要定义一个卸载验证器,需要实现一个以上接口的类,并定义成服务,给出以下服务标签:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">module_install.uninstall_validator</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">给出该标签后,在容器编译阶段会自动收集该服务并注入到模块安装器中,随后在卸载表单中被调用。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">为了更好的性能,通常将卸载验证器服务定义成延迟实例化服务,即服务定义中加入“</span>lazy: true<span style="font-family:宋体">”,但由于目前的服务收集器“</span>service_collector<span style="font-family:宋体">”实现上需要改进,在改进前,实际上定义了</span>lazy<span style="font-family:宋体">也起不到延迟实例化的作用,云客已经将该</span>bug<span style="font-family:宋体">提交给官方,但依然建议定义</span>lazy<span style="font-family:宋体">,以便改进后获得好的性能,注意定义了</span>lazy<span style="font-family:宋体">后需要为服务建立代理类,系统不会自动建立,如何建立代理类呢?</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">打开命令行工具,先通过“</span>cd<span style="font-family:宋体">”命令进入</span>drupal<span style="font-family:宋体">安装根目录,运行如下命令:</span></span></span></p> <pre> <code class="language-bash">php core/scripts/generate-proxy-class.php 'className ' "dir"</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">其中</span>className<span style="font-family:宋体">是延迟服务的全限定类名,</span>dir<span style="font-family:宋体">是相对于系统根目录的代理类输出目录</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">重要提示:实测在</span>win<span style="font-family:宋体">平台下,用默认的命令行工具将出现类不存在的错误,所以请使用类</span>linux<span style="font-family:宋体">平台的命令行工具,如</span>Git<span style="font-family:宋体">终端命令行工具,云客已经将该</span>bug<span style="font-family:宋体">提交给官方。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">系统提供的默认卸载验证器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">服务</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">id</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">content_uninstall_validator</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Entity\ContentUninstallValidator</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果卸载的模块提供了一个内容实体类型,然而有实体数据存在,则不允许卸载</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">服务</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">id</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">required_module_uninstall_validator</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Extension\RequiredModuleUninstallValidator</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果模块的</span>info<span style="font-family:宋体">文件中设置了</span>'required'<span style="font-family:宋体">不为空,那么不允许卸载</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">服务</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">id</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">field.uninstall_validator</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\field\FieldUninstallValidator</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果模块提供了一个在使用中的字段则不允许卸载</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">服务</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">id</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">filter.uninstall_validator</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\filter\FilterUninstallValidator</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果提供了一个正在被使用的过滤器插件则不允许卸载</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块卸载过程:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在模块的卸载管理页呈现时,系统已经调用模块卸载验证器做了模块是否能够被卸载的验证,见:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">$moduleInstaller-&gt;validateUninstall(array_keys($uninstallable));</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果卸载验证器返回模块不能被卸载,或者有其他模块依赖在其上,那么在卸载管理页上,对应的卸载选择框将被禁用,并给出不能被卸载的原因</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用户选择需要卸载的模块提交后,系统将暂存提交的被卸载模块到键值储存中,并跳转到以下确认路由:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">&nbsp;&nbsp;system.modules_uninstall_confirm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当确认后,将调用模块安装器卸载模块:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal::service('module_installer')-&gt;uninstall($modules , $uninstall_dependents = TRUE);</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数</span>$modules<span style="font-family:宋体">:为欲被卸载的模块机器名构成的数组。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数</span>$uninstall_dependents<span style="font-family:宋体">:布尔值,如果传递为</span>true<span style="font-family:宋体">,那么依赖于被卸载模块的那些模块会被一并卸载,在同步期间该参数被设置为</span>false<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回值:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">全部模块成功卸载时返回</span>true<span style="font-family:宋体">,部分成功或完全不成功时将抛出异常,中断卸载过程,</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该方法的卸载过程如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">一、验证并排序被卸载模块</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、如果在传入的被卸载模块中,有模块文件不存在的,将返回</span>false<span style="font-family:宋体">,卸载过程结束</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、排除已被卸载的模块,排除后如果没有模块剩下,将返回</span>true<span style="font-family:宋体">,卸载过程结束</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、当</span>$uninstall_dependents<span style="font-family:宋体">为</span>true<span style="font-family:宋体">时,将那些依赖被卸载模块且已经安装的模块添加到卸载模块列表中,以便一起卸载</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、调用模块卸载验证器,如卸载模块列表中有模块不允许被卸载,那么抛出异常,卸载过程终止。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">5<span style="font-family:宋体">、按模块扩展的</span>sort<span style="font-family:宋体">属性排序被卸载的模块,这样做将让被依赖的模块在依赖她的模块之后卸载。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">二:开始循环逐个卸载模块</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、如果被卸载模块提供有实体类型,那么调用实体</span>bundle<span style="font-family:宋体">侦听器的</span>bundle<span style="font-family:宋体">删除方法,以通知需要关注</span>bundle<span style="font-family:宋体">被删除的组件,详见本系列实体结构更新主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、派发钩子“</span>module_preuninstall<span style="font-family:宋体">”,以便所有模块包括正被卸载的模块对卸载进行响应</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、加载被卸载模块的“</span>$moduleName.install<span style="font-family:宋体">”文件</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、执行被卸载模块的“</span>uninstall<span style="font-family:宋体">”钩子(如果存在的话),仅被卸载模块的该钩子被执行</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">5<span style="font-family:宋体">、卸载模块的默认配置,以及属于她的可选配置</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">6<span style="font-family:宋体">、在容器中替换路由提供器服务为“</span>router.route_provider.lazy_builder<span style="font-family:宋体">”服务,该服务能让后续卸载流程中有用到路由提供器时,实时重建路由,这样做能让正被卸载的模块提供的路由失效,如果后续卸载流程中没有用到路由提供器,那么路由不会重建,将在卸载结束时显式重建路由。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">7<span style="font-family:宋体">、进行实体类型结构更新:如果模块提供了实体类型或者已存可字段化实体类型的实体字段,那么调用实体更新管理器进行卸载,详见本系列实体结构更新主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">8<span style="font-family:宋体">、如果被卸载模块通过</span>hook_schema()<span style="font-family:宋体">钩子提供有数据库表,那么摧毁这些表</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">9<span style="font-family:宋体">、从核心扩展配置对象(</span>core.extension<span style="font-family:宋体">)中删除被卸载的模块</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">10<span style="font-family:宋体">、更新模块处理器中的模块列表,更新后,被卸载模块提供的钩子将不再被执行了</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">11<span style="font-family:宋体">、如模块提供有标签为“</span>cache.bin<span style="font-family:宋体">”的服务,且服务实现了缓存后端接口,则执行移除</span>cache.bin<span style="font-family:宋体">动作</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">12<span style="font-family:宋体">、重置“</span>extension.list.module<span style="font-family:宋体">”服务,因为该服务保存有模块的安装状态</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">13<span style="font-family:宋体">、清理全部可缓存的插件定义,注意该动作会重置“</span>config.typed<span style="font-family:宋体">”服务,这样当用到配置</span>schema<span style="font-family:宋体">时,会重新扫描配置目录中的配置</span>schema<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">14<span style="font-family:宋体">、更新系统核心服务(服务</span>id<span style="font-family:宋体">:</span>kernel<span style="font-family:宋体">),因其中保存了已安装模块列表,更新后重新取回模块处理器</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">15<span style="font-family:宋体">、重置“主题注册”服务,让被卸载模块提供的主题钩子数据被清除失效</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">16<span style="font-family:宋体">、刷新“主题处理器</span>theme_handler<span style="font-family:宋体">”服务,因为模块可以修改主题信息</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">17<span style="font-family:宋体">、进行日志记录</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">18<span style="font-family:宋体">、从键值储存(</span>system.schema<span style="font-family:宋体">)中删除模块</span>schema<span style="font-family:宋体">版本号</span><span style="font-family:宋体">信息</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">19<span style="font-family:宋体">、进行“</span>post_update<span style="font-family:宋体">”类型的更新处理,详见本系列更新相关主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">三、所有模块卸载完成</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、无条件重建路由</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、清除被卸载模块的</span>schema<span style="font-family:宋体">版本号信息的静态缓存</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、派发“</span>modules_uninstalled<span style="font-family:宋体">”钩子</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、派发“</span>cache_flush<span style="font-family:宋体">”钩子,该钩子的含义是通知模块去失效持久的或静态的缓存,保证任意隐式依赖于被卸载模块的缓存失效</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">5<span style="font-family:宋体">、删除全部“</span>cache_bins<span style="font-family:宋体">”中的内容(数据库缓存表中的数据都被删除了),该动作对系统性能影响巨大,在高访问量的站点中,我们需要注意“缓存雪崩”问题,在模块卸载后需要及时创建缓存</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块卸载注意事项:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、如果模块提供了不属于自己的配置(不管是默认配置还是可选配置),在卸载时均不会自动从活动配置中删除(如果是配置实体且声明了对自己的依赖除外),因此这些配置需要模块在卸载钩子中自行删除,如果不删除将产生遗留的垃圾数据,系统无法识别这些配置是否已弃用,即便人工识别也很困难,它们将永久存留在系统中,因此云客建议你不要在生产站点上随意安装不成熟的模块。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">模块卸载相关钩子:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">钩子名:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">module_preuninstall</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于所有已安装模块对某模块的卸载进行响应,正被卸载的模块可实现该钩子去响应自己的卸载,该钩子通常放置在主模块文件“</span>module_name.module<span style="font-family:宋体">”中</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数为正在被卸载的模块的机器名,返回值被忽略,钩子内如果抛出异常那么卸载过程将终止;该钩子在卸载过程开始时派发,此时如果被卸载模块提供了某实体类型,仅实体</span>bundle<span style="font-family:宋体">侦听器已执行</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">默认安装下仅</span>locale<span style="font-family:宋体">模块实现了该钩子,用于删除翻译历史、</span>po<span style="font-family:宋体">文件等(并非删除已导入的翻译数据,翻译数据无法自动删除,因为翻译被所有模块共享,系统并没有记录哪个源字符串由哪个模块提供,因此也无法删除)</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">钩子名:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">uninstall</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于在模块卸载初期执行一些代码,该钩子放置在“</span>module_name.install<span style="font-family:宋体">”文件中。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">没有参数,返回值被忽略,仅正在被卸载的模块的该钩子被执行;钩子内如果抛出异常那么卸载过程将终止;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">派发该钩子时,尚未进行实质性的卸载动作,模块仍处于正常已安装的状态。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">钩子名:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">modules_uninstalled</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在模块被卸载后派发,让其他已安装的模块对卸载进行响应,刚被卸载的模块无法执行该钩子,该钩子通常放置在主模块文件“</span>module_name.module<span style="font-family:宋体">”中</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数为被卸载模块的机器名构成的数组</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">钩子名:</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">cache_flush</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">无参数,用于通知模块去失效持久的或静态(</span>static<span style="font-family:宋体">,变量中的)的缓存</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在模块卸载后被派发,默认安装中,仅</span>locale<span style="font-family:宋体">模块实现了该钩子,如需进行彻底缓存冲刷可调用以下函数:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">drupal_flush_all_caches()</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在此函数中不仅派发</span>cache_flush<span style="font-family:宋体">钩子,还失效数据库缓存表、变量静态缓存、重建路由等。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">补充:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、主题扩展的安装和卸载请见主题安装器服务:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal::service('theme_installer')-&gt;$op([$name]);</span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">$op<span style="font-family:宋体">的值为</span>install<span style="font-family:宋体">或</span>uninstall</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、有时我们可能会遇到这样的现象:在模块安装时,如果已安装模块提供的控制器有</span>PHP<span style="font-family:宋体">语法错误,会导致抛出反射方面的异常,中断安装过程,但在安装页面中却看到模块已经被安装了,这是怎么回事呢?这是因为安装模块后会执行路由重建工作而导致的,在大多数情况下可在修复后补充执行路由重建工作即可,但为了完整起见还是建议卸载并重新安装模块</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、</span>bug<span style="font-family:宋体">提示:在容器服务编译中,通过标签收集服务时(即:</span>service_collector<span style="font-family:宋体">),如果被收集的服务是“</span>lazy<span style="font-family:宋体">”的,那么应该收集原始服务名而不是代理服务名,可参见卸载验证器,这导致</span>lazy<span style="font-family:宋体">起不到作用,举个例子:在收集内容卸载验证器的时候应该收集以下服务名:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">content_uninstall_validator</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">而不是:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">drupal.proxy_original_service.content_uninstall_validator</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> </div> <section data-drupal-selector="comments" class="comments"> <h2 class="comments__title">反馈互动</h2> <div class="add-comment"> <div class="add-comment__form"> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=208&amp;2=comment&amp;3=comment" token="tA0KFAGPEfESn6Rp-APusVsnrbT-5ueLGV5n_aBxL0M"></drupal-render-placeholder> </div> </div> </section> Fri, 19 Jun 2020 00:15:14 +0000 云客 208 at http://indrupal.com http://indrupal.com/node/208#comments 148. 配置同步(导入、导出) http://indrupal.com/node/207 <span>148. 配置同步(导入、导出)</span> <span><span>云客</span></span> <span><time datetime="2020-06-12T08:15:55+08:00" title="2020-06-12 08:15 星期五">周五, 06/12/2020 - 08:15</time> </span> <div class="text-content clearfix field field--name-body field--type-text-with-summary field--label-hidden field__item"><p><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">配置同步概述:</span></b></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">配置同步是指在同一个</span>Drupal<span style="font-family:宋体">实例的不同副本间进行配置数据的导出和导入操作,以使各副本系统达到配置相同的状态。一个</span>Drupal<span style="font-family:宋体">实例</span><span style="font-family:宋体">即是指我们用</span>Drupal<span style="font-family:宋体">安装搭建的一个系统,副本是指通过克隆(或站点迁移)生成的一个完全相同的系统,一个实例可以有很多副本,这些副本不存在主和从,或原和副之间的区别,它们互为副本,这些副本属于同一个</span>Drupal<span style="font-family:宋体">实例,注意这里所讲的副本并非指共用同一个数据库的多个</span>Drupal<span style="font-family:宋体">服务器,这属于负载均衡方面的事情,安装实例副本通常用于建立生产站点和开发站点,而配置同步则用于维持两者间配置数据的一致;“不同副本”中的“不同”也可指副本在时间上的不同,从而配置同步也可用于同一个副本上配置数据的版本化管理。</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">同一个实例的所有副本间有一些信息是共享的(相同的),典型的有配置实体的</span>UUID<span style="font-family:宋体">、站点配置文件中的哈希盐,配置同步仅在同一个实例下的不同时间或空间上的副本间进行配置数据同步。</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">表面看配置同步就是系统配置数据的转存,但实际上并不那么简单,配置数据改变除会引起系统行为改变外,可能还需要执行额外代码,比如新建内容类型时可选择的自动附加字段等,另外系统中模块的启禁用状态也是通过配置数据来保存的(保存在配置对象</span>core.extension<span style="font-family:宋体">中),如果两个副本间启禁用的模块不相同,那么它们的配置对象</span>core.extension<span style="font-family:宋体">就不同,在导入时该如何处理呢?实际上遇到这种情况需要先进行模块的安装或卸载,以使两副本系统间安装的模块一致,完成后再进行配置同步,有些配置可能依赖在内容上,副本间内容可能不同,同步后还需要进行这方面的处理。</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在进行详细讲解前,我们先看一看配置导出、导入的管理入口部分。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">全部配置导出:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">导出页地址:</span>/admin/config/development/configuration/full/export</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">路由名:</span>config.export_full</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk38902026"></a><span style="font-family:宋体">控制器采用表单</span><span style="font-family:宋体">:</span>\Drupal\config\Form\ConfigExportForm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当点击导出后实际上被跳转到以下路由了:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">config.export_download</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">控制器如下:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\config\Controller\ConfigController::downloadExport</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">导出过程如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">调用“配置导出专用配置储存器”(该储存器的原理和作用见下文)将所有的配置导出添加到一个压缩包中,然后调用系统模块提供的文件下载控制器以提供下载,在这个控制器中将派发“</span>file_download<span style="font-family:宋体">”钩子,配置模块在该钩子中进行了权限检查和文件命名;详见:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\system\FileDownloadController::download</span></span><br /> <span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">&nbsp; &nbsp; &nbsp;function config_file_download($uri)</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">单一配置导出:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">导出页地址:</span>/admin/config/development/configuration/single/export</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">路由名:</span>config.export_single</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">控制器采用表单:</span>\Drupal\config\Form\ConfigSingleExportForm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">单一配置导出通常用于查看配置。这和全部配置导出不同,首先它不提供下载,而是在页面上以</span>yaml<span style="font-family:宋体">格式直接显示配置,其次导出显示的配置数据就是活动配置中的数据,没有经过模块导出修改。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">配置导入限制:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">配置导入涉及配置对象的新建、更新、删除、重命名四个方面,在讲述配置导入前,我们先看一看在系统规划层面导入有哪些限制:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、简单配置均可导入,配置实体能否进行导入,取决于其实体储存处理器是否实现了以下接口:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Config\Entity\ImportableEntityStorageInterface</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果没有实现那么将不能被导入,如果实现了则导入时调用其对应方法来处理</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、简单配置不允许有重命名操作,这会导致导入验证不通过,而配置实体可以,这是因为配置实体默认有</span>UUID<span style="font-family:宋体">属性,系统可以据此判断是否为同一个实体在进行改名操作。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、同步配置实体不允许改变实体类型</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">更多限制将在下文提到。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">单个配置导入:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">导入页地址:</span>/admin/config/development/configuration/single/import</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">路由:</span>config.import_single</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">控制器采用表单:</span>\Drupal\config\Form\ConfigSingleImportForm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这是一个由单个表单实现的多步表单,实现了再确认功能,关于多步表单详见本系列《多步表单与表单重建》主题,这里不再多述;导入过程和完整配置导入一样均采用配置导入器在批处理中进行,配置导入器详见下文,批处理请参考本系列《批处理</span>API<span style="font-family:宋体">》主题。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在导入时需提供合法的</span>yaml<span style="font-family:宋体">格式数据,如导入的配置已经存在且相同,那么没有导入的必要,因此不会做任何处理,另外:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">当导入的是配置实体时:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">导入配置数据中的实体</span>id<span style="font-family:宋体">和自定义实体</span>id<span style="font-family:宋体">必须二选一提供,如果均有提供那么以自定义实体</span>id<span style="font-family:宋体">为准,如果导入的配置实体已经存在,那么导入配置中必须存在</span>uuid<span style="font-family:宋体">且需要和已存在的配置实体相同,如果配置实体尚不存在,那么</span>uuid<span style="font-family:宋体">可选提供,但不能提供一个已经存在的</span>uuid<span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">当导入的是简单配置时:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">必须提供配置名,即配置对象名(配置文件不带“</span>.yml<span style="font-family:宋体">”扩展名后缀),此时自定义实体</span>id<span style="font-family:宋体">被忽略</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">额外验证:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">不管是什么配置都会在配置导入器层面进行额外验证,验证以下事情:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、简单配置不允许有重命名操作;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、从命名操作不允许导致实体类型改变</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在这里模块可以参与验证过程,详见配置导入器一节。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">完整配置导入:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">导入页地址:</span>/admin/config/development/configuration/full/import</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">路由名:</span>config.import_full</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">控制器采用表单:</span>\Drupal\config\Form\ConfigImportForm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该页面的作用仅是上传配置文件压缩包并解压到配置同步目录(上传的压缩包通常来自副本实例的完整配置导出),然后跳转到配置同步操作页面</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">配置同步操作页:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">上传完整配置压缩包后会自动跳转到同步操作页面,该页面地址为:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">/admin/config/development/configuration</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">路由名:</span>config.sync</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">控制器采用表单:</span>\Drupal\config\Form\ConfigSync</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该页面会比较同步目录中的配置(准确说是经过模块导入修改后的配置,见下)和活动配置之间的差异,并允许用户进行配置导入,除此外系统还很贴心的显示出最后一次导入后系统活动配置的变化情况(这利用了配置快照功能,见下)。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在理解配置同步的细节前,我们先看一看需要用到的一些组件。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">配置储存器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">配置储存器用于支持配置对象(包含配置实体)的底层配置数据的</span>CRUD<span style="font-family:宋体">操作,配置同步即是用此读写配置数据,依据储存位置,系统中有两大类储存器:数据库型和文件型,都实现了相同的接口:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Config\StorageInterface</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">系统提供了多个储存器服务,这里做一个简介:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">活动配置储存器:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">活动(</span>active<span style="font-family:宋体">)配置也就是在系统中正生效的配置,默认情况下是储存在数据库中的(这可被改变),该储存器也是我们使用的主要配置储存器,其被服务“</span>config.storage<span style="font-family:宋体">”包装使用,定义如下:</span> </span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>config.storage.active</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Config\DatabaseStorage</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该储存器读取的便是活动配置,读写将影响站点的行为</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">同步目录专用配置储存器:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">是一个文件类型的储存器,在导入完整配置时会用到,服务定义如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>config.storage.staging</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务别名:</span>config.storage.sync</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Config\FileStorage</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该服务由以下工厂实例化:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Config\FileStorageFactory::getSync</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于操作同步配置目录中的配置文件,同步目录的位置在站点配置文件的“</span>$settings['config_sync_directory']<span style="font-family:宋体">”中指定,如果没有设置那么默认使用“</span>$config_directories['sync']<span style="font-family:宋体">”的值,该变量会在站点安装时初始化,其值类似如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">'sites/default/files/config_LrSduAqLq52nfAsive-wJy146xfopHbhzM717UY9a0qj58IxEdWDmkeKbY4eILC5ERrFrMILiw/sync';</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk38965244"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">配置导出专用配置储存器</span></span></span></a><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>config.storage.export</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Config\ManagedStorage</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">由于代码涉及到弃用更新,所以这里对该储存器仅讲原理。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">配置导出为什么不直接使用活动配置储存器呢?这是因为</span>Drupal<span style="font-family:宋体">提供了一种能力:在导出前给模块一个修改配置数据的机会;如果直接使用活动配置那么这种修改将直接生效,而不是仅作用于导出的数据。为了达到这个目的专门设计了这个导出储存器,在导出前,她会将活动配置中的所有配置完整的复制到暂存储存器中,然后将暂存储存器提供给模块修改,这个暂存储存器使用数据库表“</span>config_export<span style="font-family:宋体">”储存即将被导出的配置数据,模块可以订阅以下事件获得修改机会:</span></span></span></p> <p style="text-indent:21.2pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">事件常量:</span>ConfigEvents::STORAGE_TRANSFORM_EXPORT</span></span></p> <p style="text-indent:21.2pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">常量值:“</span>config.transform.export<span style="font-family:宋体">”</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模块将从以下事件对象中获得暂存储存器进行修改:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Config\StorageTransformEvent::getStorage</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">通常如果一个模块在导出时做了修改,那么在导入时也需要修改,与之对应的,系统也提供了导入专用配置存储器(详见下文的配置导入转化器)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">另外该导出储存器也实现了为导出动作申请锁,这样可以保证在同一时刻仅有一个请求在执行导出</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">数据替换储存器:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这不以服务方式存在,而是一个工具类:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\config\StorageReplaceDataWrapper</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于包装一个配置储存器,在被包装的储存器之上提供配置替换数据,可以通过其</span>replaceData<span style="font-family:宋体">方法提供替换用的配置数据,一旦有提供,那么该储存器的</span>CURD<span style="font-family:宋体">操作均以替换数据优先,这在单个配置导入中被用到。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">快照配置储存器:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>config.storage.snapshot</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Config\DatabaseStorage</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">数据库类型储存器,使用数据库表“</span>config_snapshot<span style="font-family:宋体">”,用于在每一次配置导入(不管是整体导入还是单条导入)操作后为所有活动配置生成一个快照,该快照用于追踪最后一次导入操作后活动配置发生的变化,这些变化将显示在配置同步页中</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">配置</span></span></span><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">schema</span></span><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">储存器:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">采用和配置储存器相同的方式去读取系统中所有已安装的模块、主题和核心中提供的配置</span>schema<span style="font-family:宋体">文件。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>config.storage.schema</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>\Drupal\Core\Config\ExtensionInstallStorage</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">实现接口:</span>\Drupal\Core\Config\StorageInterface</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">实际上该类并不止用于该服务:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">首先她不仅可以读取配置</span>schema<span style="font-family:宋体">文件目录,还可以读取默认配置和可选配置目录,具体读取哪种目录类型取决于传递给构造函数的第二个参数</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">其次她读取的并不是某一个扩展的某种目录类型,而是系统中所有已经安装的模块、主题和核心的某目录类型,即相同目录类型的多个目录,因此其构造函数的第一个参数需要传递活动配置储存,通过活动配置才能知道哪些扩展是已经被安装的。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">因此该类在配置安装器等地方也被直接实例化去读取所有已安装模块、主题、核心的默认和可选配置。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该类继承自以下安装储存器类:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Config\InstallStorage</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">不同的是该父类读取的扩展包含了没有被安装的扩展,即父类并不区分扩展是否已被安装。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">以上安装储存继承自文件储存,但仅用于读取目录中的文件,不提供写入支持。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">读者可能会问:如果有两个或多个扩展提供了同名</span>yml<span style="font-family:宋体">文件,那么读取谁呢?这取决于扫描顺序,以最后一个为准,但如果包含了安装配置扩展,那么其总是优先。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><a name="_Hlk38965535"><b><span style="font-family:宋体">配置导入转化器</span></b></a></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>config.import_transformer</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Config\ImportStorageTransformer</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">前文讲到了配置导出专用配置储存器,在配置导出时有一个暂存阶段,允许模块修改导出的配置,与之对应的,在导入时也有一个暂存阶段供模块修改导入的配置,导入采用导入专用配置储存器,配置导入转化器即是用于将操作同步目录的同步配置储存器转化为导入专用配置储存器,该储存器使用数据库表“</span>config_import<span style="font-family:宋体">”来存储即将导入的配置数据,模块对导入配置的修改结果即保存在其中,如果模块需要修改导入配置,那么需要订阅以下事件:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">事件常量:</span>\Drupal\Core\Config\ConfigEvents::STORAGE_TRANSFORM_IMPORT</span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">常量值:</span>config.transform.import</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">从事件对象的</span>getStorage()<span style="font-family:宋体">方法可得到导入专用配置储存器,在该储存器上进行修改操作。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">储存比较器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这不是一个服务,而是一个工具类。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>\Drupal\Core\Config\StorageComparer</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">接口:</span>\Drupal\Core\Config\StorageComparerInterface</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于比较两个配置储存器中所储存的配置数据差异,并持有配置储存器,为配置导入操作提供支持,构造函数的第一个参数应为将导入的源储存(保存外部将被导入的配置),第二个参数应为当前系统的活动配置;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该工具类的关键方法如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function createChangelist()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该方法将计算两个储存器在创建、更新、删除、重命名这</span>4<span style="font-family:宋体">个方面的变化,调用该方法后就能得到配置改变列表,系统依据改变列表进行导入操作,改变列表在内部表示为一个数组:</span></span></span></p> <p style="text-align:justify; text-indent:21pt">&nbsp;</p> <pre> <code class="language-php"> [ 'create' =&gt; [], 'update' =&gt; [], 'delete' =&gt; [], 'rename' =&gt; [], ]; </code></pre> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">每个元素的值数组元素为配置对象名(</span>rename<span style="font-family:宋体">除外,其值数组元素为“</span>$old_name . '::' . $new_name<span style="font-family:宋体">”),他们已经依据配置依赖排序,比如“</span>delete<span style="font-family:宋体">”的值中,叶子节点靠前,遍历时先被处理,这就保证了叶子节点先被删除</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">配置导入器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这不是一个服务,而是一个工具类。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>\Drupal\Core\Config\ConfigImporter</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该类负责执行具体的导入逻辑,导入默认进行以下四项事务(模块可以定义更多事务):</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">同步扩展状态:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">也就是让副本间的模块或主题有相同的安装和卸载状态,如果状态不同,那么将在目标副本上进行扩展的安装或卸载操作</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">导入配置:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">配置数据导致,简单配置将直接导入,而配置实体则调用配置实体储存处理器来处理,不支持配置导入的配置实体不能导入(支持须实现可导入配置储存处理器接口),将给出错误提示</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">处理配置内容依赖丢失:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">部分配置可能依赖于某些内容,而副本间内容可能不相同,从而导致依赖于内容的配置出现隐患,系统将派发事件让配置所属模块自行处理</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">同步后处理:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">取消锁,派发事件,创建配置快照等</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">方法解释如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function import()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">配置同步通常在批处理流程中执行,但也可以调用该方法在非批处理环境中执行</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function alreadyImporting()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">通过锁来判断导入是否已经开始了,避免启动多个导入流程</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function validate()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">对同步操作进行验证,验证内容有:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、简单配置不允许有重命名操作;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、从命名操作不允许导致实体类型改变</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模块可以参与导入验证,须订阅以下事件进行自定义验证:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Config\ConfigEvents::IMPORT_VALIDATE</span></span></p> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模块的验证函数可以从事件对象中取得配置导入器对象,通过导入器对象可以取得源和目标配置储存器,如果有验证错误可以直接调用配置导入器的</span>logError($message)<span style="font-family:宋体">方法进行错误设置</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function createExtensionChangelist()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">初始化扩展改变列表,即属性:</span>$this-&gt;extensionChangelist<span style="font-family:宋体">,换句话说该方法判断配置导入过程中,有哪些模块或主题需要安装或卸载,注意:在进行两个副本间同步时,需要安装的模块如果没有被上传到模块目录,那么不会得到安装,这不会提示错误,由于配置的复杂性,强烈建议副本间的模块目录有相同模块文件。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function initialize()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">验证配置导入,并产生配置导入批处理的过程,相当于设置批处理回调,返回一个步骤数组,其中每个元素指示一个批处理操作回调,元素值:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果是字符串且配置导入器对象存在该方法,那么回调即是该方法</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果是一个合法的回调,那么直接采用</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模块可以实现修改钩子“</span>config_import_steps<span style="font-family:宋体">”添加自定义步骤,钩子的第一个参数是步骤数组(应以引用接收进行修改),第二个参数是配置导入器对象;在该方法可以看到前文所述的几个配置同步默认事项。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function processExtensions(&amp;$context)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">批量安装或卸载扩展(如果配置同步不会引起扩展状态改变那么不会被执行),顺序是先模块后主题,先安装后卸载,即先把模块的安装卸载处理完再处理主题的安装和卸载;当</span>$context['finished']<span style="font-family:宋体">小于</span>1<span style="font-family:宋体">时该方法会循环调用,详见本系列批处理主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function processExtension($type, $op, $name)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">执行某个模块或主题的安装或卸载,</span>$type<span style="font-family:宋体">指示扩展的类型(值为</span>module<span style="font-family:宋体">或</span>theme<span style="font-family:宋体">),</span>$op<span style="font-family:宋体">指示操作类型(值为</span><a name="_Hlk40706616">install</a><span style="font-family:宋体">或</span>uninstall<span style="font-family:宋体">),</span>$name<span style="font-family:宋体">为扩展机器名;注意该方法不会自动安装或卸载模块的依赖,仅考虑模块本身,因为被导入的副本配置应已考虑了依赖关系,这里仅是同步而已,这就导致了一个注意事项:由于采用了这种处理方式,如果某个模块在安装或卸载期间导致整个同步工作中断,那么系统可能会发生依赖错误,这种错误需要视具体情况去排查,因此我们在同步时,务必将流程走完。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function setProcessedExtension($type, $op, $name)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">记录已经安装或卸载的模块或主题</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function processConfigurations(&amp;$context)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">批量进行配置导入,导入顺序是先默认集再其他集,逐次处理,在每个集中按删除、创建、重命名、更新的顺序处理;在该方法中重置配置储存比较器是必须的,因为可能有模块的安装与卸载动作,重置后才能获取新的改变列表;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function checkOp($collection, $op, $name)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回布尔值,检查在某个配置对象上的某种同步操作还是否有必要:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当重命名时,目标储存中已经存在了命名后的结果,那么只需要进行更新即可;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当删除时,目标中已经没有了,就不必进行;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当创建时,如目标中已经存在了,那么需要删除后重建</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当更新时,目标中却没有,那么不必更新</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function importInvokeOwner($collection, $op, $name)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">处理配置实体的导入,仅配置实体的储存处理器实现了可导入配置实体储存处理器接口才能导入,导入逻辑由接口对应方法处理</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function importInvokeRename($collection, $rename_name)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">配置重命名导入,注意:仅配置实体才能进行重命名,且配置实体的储存处理器需要实现可导入配置实体储存处理器接口,导入逻辑由接口对应方法处理;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">默认实现见方法:</span>\Drupal\Core\Config\Entity\ConfigEntityStorage::importRename</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function importConfig($collection, $op, $name)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">处理所有的简单配置导入,以及非默认集上的配置实体导入</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function processMissingContent(&amp;$context)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该方法在配置导入完成后执行,处理配置的内容依赖丢失的情况:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">如果某个配置依赖于某个或某些内容(在依赖声明中“</span>content<span style="font-family:宋体">”项不为空),那么负责该配置的模块必须要订阅以下事件:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">事件名:</span>\Drupal\Core\Config\ConfigEvents::IMPORT_MISSING_CONTENT</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">事件值:</span>config.importer.missing_content</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">事件对象为:</span>\Drupal\Core\Config\Importer\MissingContentEvent</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">可通过事件对象得到丢失的内容,其是以下方法的返回值:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Config\ConfigManager::findMissingContentDependencies</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在订阅器中必须进行以下处理之一:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">删除配置对象、消除依赖关系、建立内容实体</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这些处理的目的是让依赖冲突消失,处理完后必须调用事件对象的</span>resolveMissingContent<span style="font-family:宋体">方法通知系统依赖冲突已经解决;为了配置同步能顺利完成,系统默认提供了以下订阅器服务</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">“</span>config.importer_subscriber<span style="font-family:宋体">”。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该订阅器进行兜底,会无条件通知系统所有的依赖都已解决。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">注:不进行以上实质性的冲突处理而直接调用事件对象的</span>resolveMissingContent<span style="font-family:宋体">方法也不会报错,但这种欺骗处理会给系统埋下隐患</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function finish(&amp;$context)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在配置同步最后阶段调用,派发以下事件(配置快照即是在该事件中生成):</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Config\ConfigEvents::IMPORT</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">取消跨请求锁,重置导入器</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">差异格式化器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>diff.formatter</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Diff\DiffFormatter</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于格式化差异对象以对比显示其不同,能够像</span>GIT<span style="font-family:宋体">一样对比两个文档发生的变化,这用在配置同步页面中显示配置发生的变化</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用法示例:</span>\Drupal\config\Controller\ConfigController::diff</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">差异对象:</span>Drupal\Component\Diff\Diff</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">差异对象的产生:</span>\Drupal\Core\Config\ConfigManager::diff</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">这是一个很棒很通用的工具,在许多</span>PHP<span style="font-family:宋体">项目中会用到,有兴趣的读者可以深入研究</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">补充:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1.<span style="font-family:宋体">在</span>drupal<span style="font-family:宋体">中生成压缩文件可以执行以下代码:</span></span></span></p> <pre> <code class="language-php"> $archiver = new \Drupal\Core\Archiver\ArchiveTar('yunke.tar.gz', 'gz'); $archiver-&gt;addString("a.yml", 'aaaa'); $archiver-&gt;addString("b.yml", 'bbbb'); die; </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">&nbsp; <span style="font-family:宋体">如需上传压缩包解压等可参考:</span>\Drupal\config\Form\ConfigImportForm::submitForm</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、配置导入过程使用了跨请求的持久锁,这种锁在请求结束时不会自动释放,如果导入过程出现故障,尚没有来得及释放锁,那么在锁有效期内(默认</span>30<span style="font-family:宋体">秒)执行下一次导入会被提示“另一个请求已经在运行导入”,这种情况下需要手动释放锁;如果同步时间太长,超过</span>30<span style="font-family:宋体">秒,则可能导致锁保护失效。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、配置只能在同一个</span>drupal<span style="font-family:宋体">实例的副本间同步,通常是生产站点和开发站点间,这会检查配置对象“</span>system.site<span style="font-family:宋体">”中的</span>uuid<span style="font-family:宋体">是否相同</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、没有</span>UUID<span style="font-family:宋体">的配置在导入时不存在重命名,只能是删除再新建,准确理解是即便符合导入要求的配置实体,如果没有</span>UUID<span style="font-family:宋体">那么也是不能重命名导入的,但需注意即便简单配置有</span>UUID<span style="font-family:宋体">也不能有重命名导入。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">5<span style="font-family:宋体">、在配置实体基类(</span>\Drupal\Core\Config\Entity\ConfigEntityBase<span style="font-family:宋体">)中,属性</span>$isSyncing<span style="font-family:宋体">用于标识当前是否处于配置同步过程中,其有重要用途,比如节点类型配置实体在新建后,会自动添加</span>body<span style="font-family:宋体">字段,显然导入配置时不再需要这个添加动作,因此必须用该旗标指明当前是否处于同步中,以便决定是否执行某些动作</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">6<span style="font-family:宋体">、配置同步官网文档地址:</span>https://www.drupal.org/documentation/administer/config</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">7<span style="font-family:宋体">、由于配置可以衍生出非常复杂的情况,有些问题系统可能没有考虑到,因此强烈建议在生产站点进行配置同步前做数据库备份,以便出现未知问题时回滚。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> </div> <section data-drupal-selector="comments" class="comments"> <h2 class="comments__title">反馈互动</h2> <div class="add-comment"> <div class="add-comment__form"> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=207&amp;2=comment&amp;3=comment" token="LxHm0JfCJFbXkHs2hG8Vi0xEk3L8AMtnT9q8yj4efIE"></drupal-render-placeholder> </div> </div> </section> Fri, 12 Jun 2020 00:15:55 +0000 云客 207 at http://indrupal.com http://indrupal.com/node/207#comments 147. 配置的安装与卸载 http://indrupal.com/node/206 <span>147. 配置的安装与卸载</span> <span><span>云客</span></span> <span><time datetime="2020-05-28T21:01:53+08:00" title="2020-05-28 21:01 星期四">周四, 05/28/2020 - 21:01</time> </span> <div class="text-content clearfix field field--name-body field--type-text-with-summary field--label-hidden field__item"><p><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">本文讲述配置的安装与卸载,这发生在模块安装和卸载时,注意这与配置同步不同,配置同步是指在被安装实例的两个副本间同步配置数据,即配置的导入或导入,详见本系列配置同步主题。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">默认配置与可选配置:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">模块“</span>config/install<span style="font-family:宋体">”目录中的配置称为默认配置,“</span>config/optional<span style="font-family:宋体">”目录中的配置称为可选配置,不管是何配置,其文件名的第一段(第一个点号前的内容)均是某个扩展的机器名,指示该配置属于哪一个扩展,注意:属于某个扩展的配置,并不一定是该扩展提供的,任何一个模块均可在默认配置或可选配置中提供属于其他扩展的配置;换句话说,在扩展的默认配置和可选配置目录里均可包含属于其他模块的配置文件,在扩展安装时:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">默认配置:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">均会被安装,其是模块运行必须要的配置,如果其依赖得不到满足(比如配置所属的模块没有被安装),那么扩展不能被安装(安装时抛出异常结束安装过程)。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="background:#d9d9d9"><span style="font-family:宋体"><span style="color:black">可选配置:</span></span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">仅在条件满足时才被安装,条件如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、可选的配置尚不存在于活动配置中</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、可选配置须是一个配置实体,简单配置不能作为可选配置</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、可选配置声明的其他依赖能被满足,即“</span>dependencies<span style="font-family:宋体">”根键中申明的依赖能被满足</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">同时仅安装对当前正在安装的扩展有依赖的可选配置(即:如无当前正在被安装的扩展那么这些可选配置无法工作),这些可选配置来自全站所有启用的扩展和核心,举个例子:已被安装的</span>A<span style="font-family:宋体">模块在其可选配置目录中提供了一个配置对象</span>X<span style="font-family:宋体">,</span>X<span style="font-family:宋体">依赖于模块</span>B<span style="font-family:宋体">,如果模块</span>B<span style="font-family:宋体">没有被安装,那么</span>X<span style="font-family:宋体">将不被安装,一旦模块</span>B<span style="font-family:宋体">被安装,则在安装</span>B<span style="font-family:宋体">时,系统会找出模块</span>A<span style="font-family:宋体">中的这个</span>X<span style="font-family:宋体">,并进行安装。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">不管是已安装模块中的可选配置,还是正在被安装的模块中的可选配置,其安装时机均是在模块安装时。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">简单配置与配置实体的区别:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">区别联系如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、通常配置实体有</span>uuid<span style="font-family:宋体">,而简单配置没有,可参考以下方法:</span></span></span></p> <pre> <code>\Drupal\Core\Entity\EntityStorageBase::create \Drupal\Core\Config\ConfigManager::getConfigDependencyManager </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">但有另外,比如简单配置“</span>system.site<span style="font-family:宋体">”就有</span>uuid<span style="font-family:宋体">(默认安装中也仅该简单配置有),所以这是一个潜规则,不是强制规则,我们不能以此去进行判断</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、配置实体必有依赖声明,即存在“</span>dependencies<span style="font-family:宋体">”根键,其值为依赖数组,依赖数组的键名为</span>module<span style="font-family:宋体">、</span>theme<span style="font-family:宋体">、</span>config<span style="font-family:宋体">、</span>content<span style="font-family:宋体">,对应的键值为依赖名构成的数组,在配置安装时不检查</span>content<span style="font-family:宋体">依赖,</span> <span style="font-family:宋体">“</span>dependencies<span style="font-family:宋体">”键在简单配置中是可选的,默认安装中,没有简单配置用到该键;系统仅对配置实体进行依赖处理,简单配置不处理依赖,换句话说仅配置实体可以有依赖,简单配置不应使用依赖。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、简单配置不能作为可选配置,仅配置实体可以。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">4<span style="font-family:宋体">、准确的讲:不满足配置实体的配置对象命名规则的即是简单配置。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">判断两者可简单使用以下代码:</span></span></span></p> <pre> <code class="language-php">\Drupal::service('config.manager')-&gt;getEntityTypeIdByName($name)</code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">传递配置对象名,返回值如果等效为</span>true<span style="font-family:宋体">,那么是配置实体,否则为简单配置</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">查询系统中所有的简单配置可使用以下代码:</span></span></span></p> <pre> <code class="language-php"> $definitions = []; foreach (\Drupal::entityTypeManager()-&gt;getDefinitions() as $entity_type =&gt; $definition) { if ($definition-&gt;entityClassImplements(\Drupal\Core\Config\Entity\ConfigEntityInterface::class)) { $definitions[$entity_type] = $definition; } } //找出所有配置实体的配置名前缀 $config_prefixes = array_map(function ($definition) { return $definition-&gt;getConfigPrefix() . '.'; }, $definitions); $names = \Drupal::service('config.storage')-&gt;listAll(); $names = array_combine($names, $names); foreach ($names as $config_name) { foreach ($config_prefixes as $config_prefix) { if (strpos($config_name, $config_prefix) === 0) { unset($names[$config_name]); //去除所有配置实体,剩下的就是简单配置 } } } $configurations = \Drupal::configFactory()-&gt;loadMultiple($names); </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">云客语:关于简单配置和配置实体的硬性界定将可能在未来版本进行规划,极有可能是通过判断是否存在“</span>dependencies<span style="font-family:宋体">”根键来实现,目前仅能通过以上方法判断。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">配置安装器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于安装扩展的默认和可选配置,并提供配置验证等辅助功能,注意其并不提供配置卸载功能,配置卸载在配置管理器中进行,见下文。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>config.installer</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Config\ConfigInstaller</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">各方法解释如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function installDefaultConfig($type, $name)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该方法在模块安装时被调用,用于安装扩展的默认配置;同时会调用可选配置安装方法去安装可选配置;如果安装的是安装配置扩展,那么已被安装的简单配置不会被再次安装;安装后会重置配置工厂以保证配置是新的;注意该方法并不安装配置</span>schema<span style="font-family:宋体">,但在扩展是主题或者扩展存在</span>schema<span style="font-family:宋体">配置目录时,会清理</span>schema<span style="font-family:宋体">缓存;注意:安装配置扩展提供的配置将覆写所有模块提供的配置,而不管是默认还是可选配置。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function installOptionalConfig(StorageInterface $storage = NULL,<a name="_Hlk41061402"> $dependency </a>= [])</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">安装可选配置,这些可选配置由传入的储存器提供,参数</span>$dependency<span style="font-family:宋体">是一个依赖表示数组,键名为</span>module<span style="font-family:宋体">、</span>theme<span style="font-family:宋体">或</span>config<span style="font-family:宋体">,键值为配置依赖名,其含义是仅安装有这些依赖的可选配置,没有这些依赖的可选配置即使满足条件也不安装,如果没有传递,那么只要满足条件就都安装。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">系统提供了以下储存器来读取所有已启用模块、主题和核心的可选配置目录:</span></span></span></p> <p style="text-indent:10.5pt; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\Core\Config\ExtensionInstallStorage</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该储存器的介绍详见本系列配置同步主题;可选配置仅在以下条件满足时才被安装:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、尚未存在于活动配置中</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、是一个配置实体,简单配置不能作为可选配置</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">3<span style="font-family:宋体">、在</span>module<span style="font-family:宋体">、</span>theme<span style="font-family:宋体">和</span>config<span style="font-family:宋体">方面的依赖能够被满足</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在安装时会考虑依赖关系,被依赖的可选配置先安装</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function getConfigToCreate(StorageInterface $storage, $collection, $prefix = '', array $profile_storages = [])</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">从一个配置存储器中得到将要安装创建的配置数据,这些配置数据是经过安装配置扩展(</span>profile<span style="font-family:宋体">)提供的配置数据覆写过的</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function createConfiguration(<a name="_Hlk41033867">$collection</a>, array <a name="_Hlk41033858">$config_to_create</a>)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">传入某个集里面的配置去创建,即添加到活动配置中,参数</span>$collection<span style="font-family:宋体">为集名,参数</span>$config_to_create<span style="font-family:宋体">是配置数据数组,键名为配置名,键值为对应的配置内容数据;在创建时会考虑依赖关系,被依赖的配置先创建;如果被创建的配置在活动配置中已经存在了,那么将进行更新;会为默认集中的配置添加默认配置哈希,该哈希能对配置数据起到验证作用;如果是配置实体,那么将在实体层面进行更新或保存,值得注意的是如果实体的不可安装的,那么将无法创建或更新,但也不会引起错误,换句话说被安装的模块在配置目录中携带的不可安装的配置实体类型的配置将不会起作用。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function installCollectionDefaultConfig($collection)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">一次性安装已启用模块的所有默认配置,这用在系统安装时</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function findPreExistingConfiguration(StorageInterface $storage)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">传入一个配置储存器,检查其中是否有配置已经存在于活动配置中,将在所有配置集上进行检查,如果没有存在的,那么将返回空数组,否则返回一个数组,键名为集名,键值为已经存在的配置的配置名构成的数组。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function checkConfigurationToInstall($type, $name)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">传入一个将被安装的扩展,参数为扩展类型和机器名,检查其默认配置(不检查可选配置)中是否有无效配置,如有将抛出异常,如无将返回</span>NULL<span style="font-family:宋体">值,这在模块安装开始阶段调用,如果抛出异常将终止模块安装流程,无效配置是指默认配置出现以下情况:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">1<span style="font-family:宋体">、配置中申明了依赖(仅限</span>module<span style="font-family:宋体">、</span>theme<span style="font-family:宋体">、</span>config<span style="font-family:宋体">类型的依赖),但依赖不能被满足(即依赖缺失)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">2<span style="font-family:宋体">、在默认配置的所有配置集中,已经有一个或多个存在于活动配置中了</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function findDefaultConfigWithUnmetDependencies(StorageInterface $storage, array $enabled_extensions, array $profile_storages = [])</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">传入扩展的默认配置储存器、已开启的所有扩展、安装配置扩展的配置储存器(包含可选配置);找出扩展默认配置中依赖得不到满足的配置,仅在默认集上检查,返回一个有两个元素的数组,两元素均是数组,其键名均是依赖得不到满足的配置的配置名,区别是:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">第一个元素:键名为配置名,键值为配置数据</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">第二个元素:键名为配置名,键值为丢失的依赖(仅包含</span>module<span style="font-family:宋体">、</span>theme<span style="font-family:宋体">、</span>config<span style="font-family:宋体">类型的依赖</span><span style="font-family:宋体">)</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function validateDependencies($config_name, array $data, array $enabled_extensions, array $all_config)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">验证一个配置的依赖是否能得到满足,返回布尔值,其中该配置所属的扩展必须已安装</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function getMissingDependencies($config_name, array $data, array $enabled_extensions, array $all_config)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">得到配置对象丢失的依赖,仅考虑</span>module<span style="font-family:宋体">、</span>theme<span style="font-family:宋体">、</span>config<span style="font-family:宋体">类型的依赖</span><span style="font-family:宋体">(不包含内容),返回一个数组,其由依赖名构成,不区分类别。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function getEnabledExtensions()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">得到系统中全部已经安装的扩展(</span>profile<span style="font-family:宋体">、模块、主题),返回一个由扩展机器名构成的数组</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function getProfileStorages($installing_name = '')</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">得到安装配置扩展(</span>profile<span style="font-family:宋体">)的配置储存器,返回一个由两个配置储存器构成的数组,这是因为默认配置和可选配置分别对应一个储存处理器,有了这些配置储存器就可以读取</span>profile<span style="font-family:宋体">提供的默认配置和可选配置了,在模块安装时,</span>profile<span style="font-family:宋体">扩展用这些配置去覆写模块提供的配置,参数指示正在安装的模块,如果就是</span>profile<span style="font-family:宋体">扩展本身那么将返回空数组(自己不需要覆写自己)。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function drupalInstallationAttempted()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回布尔值,指示当前系统是否处于系统安装过程中,系统安装指整个</span>Drupal<span style="font-family:宋体">系统安装,并非指某个模块安装</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><b><span style="font-family:宋体">配置管理器:</span></b></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">服务</span>id<span style="font-family:宋体">:</span>config.manager</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">类:</span>Drupal\Core\Config\ConfigManager</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">实现接口:</span>\Drupal\Core\Config\ConfigManagerInterface</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">用于为配置系统提供辅助功能,即提供如下这些辅助方法:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function get<a name="_Hlk38377456">Entity</a>TypeIdByName($name);</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">依据配置对象的名字,返回配置实体类型,如果配置对象属于简单配置,那么返回</span>NULL</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function loadConfigEntityByName($name);</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">依据配置对象的名字,返回配置实体,如果配置对象属于简单配置,那么返回</span>NULL</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL, $collection = StorageInterface::DEFAULT_COLLECTION);</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">从两个配置储存中读取同一个配置进行比较,返回差异对象,其用法见:</span></span></span></p> <p style="text-align:justify; text-indent:21pt"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">\Drupal\config\Controller\ConfigController::diff</span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function createSnapshot(StorageInterface $source_storage, StorageInterface $snapshot_storage);</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">为某个配置储存器创建配置快照</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function uninstall($type, $name);</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">卸载一个给定扩展的配置,参数</span>$type<span style="font-family:宋体">为扩展类型,如</span>module<span style="font-family:宋体">或</span>theme<span style="font-family:宋体">,参数</span>$name<span style="font-family:宋体">为扩展机器名,卸载操作会删除属于该扩展的所有配置,包括简单配置、配置实体、各种配置集,只要配置对象以该扩展机器名为前缀就属于该扩展,如果扩展提供了不属于自己的配置,那么这些配置不会被卸载;如果有提供配置</span>schema<span style="font-family:宋体">那么会同时清理,在卸载时会考虑依赖问题,但仅配置实体才能使用依赖,简单配置的依赖不被处理,所有直接或间接依赖传入扩展的配置实体都被处理,要么删除要么重新保存,这取决于配置实体的</span>onDependencyRemoval<span style="font-family:宋体">方法如何处理(见下文)。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在模块安装器中卸载模块时将调用以下代码:</span></span></span></p> <pre> <code class="language-php">\Drupal::service('config.manager')-&gt;uninstall('module', $module);</code></pre> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function getConfigDependencyManager()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回配置依赖管理器,关于此管理器的介绍详见本系列《有向无环图及依赖处理》主题,由该方法可见系统默认把有</span>uuid<span style="font-family:宋体">的配置当做配置实体,也仅配置实体才可以使用依赖</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function <a name="_Hlk38381528">findConfigEntityDependents</a>($type, array $names, ConfigDependencyManager $dependency_manager = NULL)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">传递某些即将被删除的被依赖项,返回依赖在这些被依赖项上的配置实体(的依赖表示对象),即传递一些依赖图中的根节点,返回依赖节点,在返回数组中叶子节点靠前,即遍历数组时会被先遍历。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="color:red">bug</span><span style="font-family:宋体"><span style="color:red">提示:</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当</span>$names<span style="font-family:宋体">数组参数有多个元素时,该方法内部使用</span>array_merge<span style="font-family:宋体">函数合并依赖数组,这将可能破坏依赖顺序,举个例子,假设有两个依赖数组:</span></span></span></p> <pre> <code class="language-php">$a=['a'=&gt;'1','b'=&gt;2,'c'=&gt;3,'d'=&gt;4]; $b=['x'=&gt;'1','b'=&gt;2,'c'=&gt;3,'d'=&gt;4]; </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">在</span>$a<span style="font-family:宋体">中,</span>a<span style="font-family:宋体">依赖</span>b<span style="font-family:宋体">,</span> b<span style="font-family:宋体">依赖</span>c<span style="font-family:宋体">,</span>c<span style="font-family:宋体">依赖</span>d<span style="font-family:宋体">,在</span>$b<span style="font-family:宋体">中,同样的</span>x<span style="font-family:宋体">依赖</span>b<span style="font-family:宋体">,</span>b<span style="font-family:宋体">依赖</span>c<span style="font-family:宋体">,</span>c<span style="font-family:宋体">依赖</span>d<span style="font-family:宋体">,如果采用</span>array_merge<span style="font-family:宋体">函数合并,那么结果是:</span></span></span></p> <pre> <code class="language-php">['a'=&gt;'1','b'=&gt;2,'c'=&gt;3,'d'=&gt;4,'x'=&gt;'1'];</code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">显然这是不对的,正确的结果应该是:</span></span></span></p> <pre> <code class="language-php">['a'=&gt;'1','x'=&gt;'1','b'=&gt;2,'c'=&gt;3,'d'=&gt;4];</code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">或者:</span></span></span></p> <pre> <code class="language-php">['x'=&gt;'1', 'a'=&gt;'1','b'=&gt;2,'c'=&gt;3,'d'=&gt;4];</code></pre> <p style="text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该</span>bug<span style="font-family:宋体">已提交给官网</span></span></span></p> <p style="text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function findConfigEntityDependentsAsEntities($type, array $names, ConfigDependencyManager $dependency_manager = NULL)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该方法将</span>findConfigEntityDependents<span style="font-family:宋体">方法返回的依赖表示对象转化为配置实体对象</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="color:red">bug</span><span style="font-family:宋体"><span style="color:red">提示</span></span><span style="font-family:宋体">:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该方法打破了依赖的顺序,需要对返回值继续做如下处理:</span></span></span></p> <pre> <code> $entities=[]; foreach ($entities_to_return as $configEntity){ $entities[$configEntity-&gt;getConfigDependencyName()]=$configEntity; } $entities_to_return=array_merge(array_intersect_key($dependencies, $entities),$entities); </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">已经提交官网</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function getConfigEntitiesToChangeOnDependencyRemoval($type, array $names, $dry_run = TRUE)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">传递某些即将被删除的被依赖项,返回一个数组,包含依赖在这些被依赖项上的配置实体;</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">参数含义如下:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">$type<span style="font-family:宋体">:被依赖项类型,有</span>module<span style="font-family:宋体">、</span>theme<span style="font-family:宋体">、</span>config<span style="font-family:宋体">、</span>content</span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">$names<span style="font-family:宋体">:一个数组,对应类型下即将被删除的被依赖项的名字(依赖名数组)</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">$dry_run<span style="font-family:宋体">:布尔值,表明是否在进行“演习”,即在该方法调用后,调用者是否真的会对返回的配置实体执行更新或删除动作,当为</span>false<span style="font-family:宋体">时表明不是在演习,会真的执行更新或删除,当为</span>true<span style="font-family:宋体">时表示在演习,返回的实际上是新克隆的配置实体对象,这样将保证原对象不受修改影响。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">返回数组有以下三个键(键值均为和将被删除的依赖相关的配置实体对象):</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">'update'<span style="font-family:宋体">:值为需要更新的实体对象,即需要重新保存</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">'delete' <span style="font-family:宋体">:值为需要删除的实体对象</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif">'unchanged'<span style="font-family:宋体">:值为无需改变的实体对象</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该方法在配置实体基类的预删除方法等地方被调用,为什么会有以上三种类型的返回呢?这是因为配置实体可以通过她的</span>onDependencyRemoval<span style="font-family:宋体">方法表明她所依赖的对象被移除后自己应被删除还是更新,当应被更新时,在依赖树中,依赖于本配置实体的其他依赖可能就无需改变了。</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">protected function callOnDependencyRemoval(ConfigEntityInterface $entity, array $dependent_entities, $type, array $names)</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该方法辅助调用配置实体的</span>onDependencyRemoval<span style="font-family:宋体">方法</span><span style="font-family:宋体">。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">仅配置实体</span><span style="font-family:宋体">有</span>onDependencyRemoval<span style="font-family:宋体">方法</span><span style="font-family:宋体">,该方法返回布尔值,当返回</span>false<span style="font-family:宋体">时表示依赖如果被移除则该配置实体应被删除,当返回</span>true<span style="font-family:宋体">时,表示不应被删除,但需要重新保存,此时在内部需要依据被删除的依赖进行自我修复调整,在该方法被调用后,系统会依据返回值进行删除或重新保存。</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">该方法的参数中,</span>config<span style="font-family:宋体">和</span>content<span style="font-family:宋体">类型的依赖已经被转化成了实体对象,键名为依赖名,什么意思呢?举个例子:</span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">假设配置实体的依赖结构如下:</span></span></span></p> <pre> <code class="language-php"> array( 'config' =&gt; array('user.role.anonymous', 'user.role.authenticated'), 'content' =&gt; array('node:article:f0a189e6-55fb-47fb-8005-5bef81c44d6d'), 'module' =&gt; array('node', 'user'), 'theme' =&gt; array('seven'), ); </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">得到的参数将如下:</span></span></span></p> <pre> <code class="language-php"> array( 'config' =&gt; array('user.role.anonymous'=&gt;$userRoleEntity1, 'user.role.authenticated'=&gt;$userRoleEntity2), 'content' =&gt; array('node:article:f0a189e6-55fb-47fb-8005-5bef81c44d6d'=&gt;$ nodeEntity), 'module' =&gt; array('node', 'user'), 'theme' =&gt; array('seven'), ); </code></pre> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">当然仅传递处于卸载过程中的依赖,不被卸载的依赖不会被传递,在内部进行修复时,只需要对属性做调整即可,无需去保存,也不用调用计算依赖方法,这些在系统流程中会被执行</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function getConfigCollectionInfo()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">通过事件派发机制获取配置集信息,默认安装中仅语言提供了配置集信息</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span lang="EN-US" style="background:#d9d9d9"><span style="color:black">public function findMissingContentDependencies()</span></span></span></span></p> <p style="text-indent:0cm; text-align:justify"><span style="font-size:10.5pt"><span style="font-family:Calibri,sans-serif"><span style="font-family:宋体">遍历整个活动配置储存,找出丢失了的类型为“</span>content<span style="font-family:宋体">”的依赖,换句话说:有些配置依赖于某些内容实体,但是内容实体被删除了,该方法就是找出那些已经被删除的内容实体</span></span></span></p> <p style="text-indent:0cm; text-align:justify">&nbsp;</p> <p style="text-indent: 0cm;">&nbsp;</p> </div> <section data-drupal-selector="comments" class="comments"> <h2 class="comments__title">反馈互动</h2> <div class="add-comment"> <div class="add-comment__form"> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=206&amp;2=comment&amp;3=comment" token="gj_MJlDiaHUOAqN-wlgZj2IAR6pu9Ra3aZO9nUKszuE"></drupal-render-placeholder> </div> </div> </section> Thu, 28 May 2020 13:01:53 +0000 云客 206 at http://indrupal.com http://indrupal.com/node/206#comments