-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.html
3560 lines (2233 loc) · 751 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html class="theme-next pisces use-motion" lang="zh-Hans">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />
<link href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />
<link href="/css/main.css?v=5.1.0" rel="stylesheet" type="text/css" />
<meta name="keywords" content="Hexo, NexT" />
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico?v=5.1.0" />
<meta name="description" content="Life and Learn!">
<meta property="og:type" content="website">
<meta property="og:title" content="LEOYANG'S BLOG">
<meta property="og:url" content="https://leoyang90.github.io/index.html">
<meta property="og:site_name" content="LEOYANG'S BLOG">
<meta property="og:description" content="Life and Learn!">
<meta property="og:locale" content="zh-Hans">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="LEOYANG'S BLOG">
<meta name="twitter:description" content="Life and Learn!">
<script type="text/javascript" id="hexo.configurations">
var NexT = window.NexT || {};
var CONFIG = {
root: '/',
scheme: 'Pisces',
sidebar: {"position":"left","display":"post"},
fancybox: true,
motion: true,
duoshuo: {
userId: '0',
author: 'LeoYang'
},
algolia: {
applicationID: '',
apiKey: '',
indexName: '',
hits: {"per_page":10},
labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
}
};
</script>
<link rel="canonical" href="https://leoyang90.github.io/"/>
<title> LEOYANG'S BLOG </title>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<script type="text/javascript">
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?c45898c39ba7512b69229aa34c07263a";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<div class="container one-collumn sidebar-position-left
page-home
">
<div class="headband"></div>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-wrapper">
<div class="site-meta ">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">LEOYANG'S BLOG</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<p class="site-subtitle">Notes and Blogs</p>
</div>
<div class="site-nav-toggle">
<button>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section">
<i class="menu-item-icon fa fa-fw fa-home"></i> <br />
首页
</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives" rel="section">
<i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
归档
</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories" rel="section">
<i class="menu-item-icon fa fa-fw fa-th"></i> <br />
分类
</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags" rel="section">
<i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
标签
</a>
</li>
<li class="menu-item menu-item-about">
<a href="/about" rel="section">
<i class="menu-item-icon fa fa-fw fa-user"></i> <br />
关于
</a>
</li>
<li class="menu-item menu-item-search">
<a href="javascript:;" class="st-search-show-outputs">
<i class="menu-item-icon fa fa-search fa-fw"></i> <br />
搜索
</a>
</li>
</ul>
<div class="site-search">
<form class="site-search-form">
<input type="text" id="st-search-input" class="st-search-input st-default-search-input" />
</form>
<script type="text/javascript">
(function(w,d,t,u,n,s,e){w['SwiftypeObject']=n;w[n]=w[n]||function(){
(w[n].q=w[n].q||[]).push(arguments);};s=d.createElement(t);
e=d.getElementsByTagName(t)[0];s.async=1;s.src=u;e.parentNode.insertBefore(s,e);
})(window,document,'script','//s.swiftypecdn.com/install/v2/st.js','_st');
_st('install', 'qzqmBx1WzwFX9LPuU1Rc','2.0.0');
</script>
</div>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<section id="posts" class="posts-expand">
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<link itemprop="mainEntityOfPage" href="https://leoyang90.github.io/2017/10/09/Laravel Database——Eloquent Model 源码分析(下)/">
<span style="display:none" itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Leo Yang">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/master.jpg">
</span>
<span style="display:none" itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="LEOYANG'S BLOG">
<span style="display:none" itemprop="logo" itemscope itemtype="http://schema.org/ImageObject">
<img style="display:none;" itemprop="url image" alt="LEOYANG'S BLOG" src="">
</span>
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2017/10/09/Laravel Database——Eloquent Model 源码分析(下)/" itemprop="url">
Laravel Database——Eloquent Model 源码分析(下)
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2017-10-09T23:11:00+08:00">
2017-10-09
</time>
</span>
<span class="post-category" >
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/laravel/" itemprop="url" rel="index">
<span itemprop="name">laravel</span>
</a>
</span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/laravel/database/" itemprop="url" rel="index">
<span itemprop="name">database</span>
</a>
</span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/laravel/database/php/" itemprop="url" rel="index">
<span itemprop="name">php</span>
</a>
</span>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<a href="/2017/10/09/Laravel Database——Eloquent Model 源码分析(下)/#comments" itemprop="discussionUrl">
<span class="post-comments-count ds-thread-count" data-thread-key="2017/10/09/Laravel Database——Eloquent Model 源码分析(下)/" itemprop="commentCount"></span>
</a>
</span>
<span id="/2017/10/09/Laravel Database——Eloquent Model 源码分析(下)/" class="leancloud_visitors" data-flag-title="Laravel Database——Eloquent Model 源码分析(下)">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-eye"></i>
</span>
<span class="post-meta-item-text">阅读次数 </span>
<span class="leancloud-visitors-count"></span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<hr>
<h2 id="获取模型"><a href="#获取模型" class="headerlink" title="获取模型"></a>获取模型</h2><h3 id="get-函数"><a href="#get-函数" class="headerlink" title="get 函数"></a>get 函数</h3><figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">get</span><span class="params">($columns = [<span class="string">'*'</span>])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $builder = <span class="keyword">$this</span>->applyScopes();</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (count($models = $builder->getModels($columns)) > <span class="number">0</span>) {</div><div class="line"> $models = $builder->eagerLoadRelations($models);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $builder->getModel()->newCollection($models);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getModels</span><span class="params">($columns = [<span class="string">'*'</span>])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->model->hydrate(</div><div class="line"> <span class="keyword">$this</span>->query->get($columns)->all()</div><div class="line"> )->all();</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>get</code> 函数会将 <code>QueryBuilder</code> 所获取的数据进一步包装 <code>hydrate</code>。<code>hydrate</code> 函数会将数据库取回来的数据打包成数据库模型对象 <code>Eloquent Model</code>,如果可以获取到数据,还会利用函数 <code>eagerLoadRelations</code> 来预加载关系模型。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">hydrate</span><span class="params">(array $items)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $instance = <span class="keyword">$this</span>->newModelInstance();</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $instance->newCollection(array_map(<span class="function"><span class="keyword">function</span> <span class="params">($item)</span> <span class="title">use</span> <span class="params">($instance)</span> </span>{</div><div class="line"> <span class="keyword">return</span> $instance->newFromBuilder($item);</div><div class="line"> }, $items));</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>newModelInstance</code> 函数创建了一个新的数据库模型对象,重要的是这个函数为新的数据库模型对象赋予了 <code>connection</code>:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">newModelInstance</span><span class="params">($attributes = [])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->model->newInstance($attributes)->setConnection(</div><div class="line"> <span class="keyword">$this</span>->query->getConnection()->getName()</div><div class="line"> );</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>newFromBuilder</code> 函数会将所有数据库数据存入另一个新的 <code>Eloquent Model</code> 的 <code>attributes</code> 中:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">newFromBuilder</span><span class="params">($attributes = [], $connection = null)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $model = <span class="keyword">$this</span>->newInstance([], <span class="keyword">true</span>);</div><div class="line"></div><div class="line"> $model->setRawAttributes((<span class="keyword">array</span>) $attributes, <span class="keyword">true</span>);</div><div class="line"></div><div class="line"> $model->setConnection($connection ?: <span class="keyword">$this</span>->getConnectionName());</div><div class="line"></div><div class="line"> $model->fireModelEvent(<span class="string">'retrieved'</span>, <span class="keyword">false</span>);</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $model;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>newInstance 函数专用于创建新的数据库对象模型:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">newInstance</span><span class="params">($attributes = [], $exists = false)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $model = <span class="keyword">new</span> <span class="keyword">static</span>((<span class="keyword">array</span>) $attributes);</div><div class="line"></div><div class="line"> $model->exists = $exists;</div><div class="line"></div><div class="line"> $model->setConnection(</div><div class="line"> <span class="keyword">$this</span>->getConnectionName()</div><div class="line"> );</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $model;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>值得注意的是 <code>newInstance</code> 将 <code>exist</code> 设置为 <code>true</code>,意味着当前这个数据库模型对象是从数据库中获取而来,并非是手动新建的,这个 <code>exist</code> 为真,我们才能对这个数据库对象进行 <code>update</code>。</p>
<p><code>setRawAttributes</code> 函数为新的数据库对象赋予属性值,并且进行 <code>sync</code>,标志着对象的原始状态:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">setRawAttributes</span><span class="params">(array $attributes, $sync = false)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">$this</span>->attributes = $attributes;</div><div class="line"></div><div class="line"> <span class="keyword">if</span> ($sync) {</div><div class="line"> <span class="keyword">$this</span>->syncOriginal();</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">syncOriginal</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">$this</span>->original = <span class="keyword">$this</span>->attributes;</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这个原始状态的记录十分重要,原因是 <code>save</code> 函数就是利用原始值 <code>original</code> 与属性值 <code>attributes</code> 的差异来决定更新的字段。</p>
<h3 id="find-函数"><a href="#find-函数" class="headerlink" title="find 函数"></a>find 函数</h3><p><code>find</code> 函数用于利用主键 <code>id</code> 来查询数据,<code>find</code> 函数也可以传入数组,查询多个数据</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">find</span><span class="params">($id, $columns = [<span class="string">'*'</span>])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (is_array($id) || $id <span class="keyword">instanceof</span> Arrayable) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->findMany($id, $columns);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->whereKey($id)->first($columns);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">findMany</span><span class="params">($ids, $columns = [<span class="string">'*'</span>])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">empty</span>($ids)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->model->newCollection();</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->whereKey($ids)->get($columns);</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="findOrFail"><a href="#findOrFail" class="headerlink" title="findOrFail"></a>findOrFail</h3><p><code>laravel</code> 还提供 <code>findOrFail</code> 函数,一般用于 <code>controller</code>,在未找到记录的时候会抛出异常。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">findOrFail</span><span class="params">($id, $columns = [<span class="string">'*'</span>])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $result = <span class="keyword">$this</span>->find($id, $columns);</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (is_array($id)) {</div><div class="line"> <span class="keyword">if</span> (count($result) == count(array_unique($id))) {</div><div class="line"> <span class="keyword">return</span> $result;</div><div class="line"> }</div><div class="line"> } <span class="keyword">elseif</span> (! is_null($result)) {</div><div class="line"> <span class="keyword">return</span> $result;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">throw</span> (<span class="keyword">new</span> ModelNotFoundException)->setModel(</div><div class="line"> get_class(<span class="keyword">$this</span>->model), $id</div><div class="line"> );</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="其他查询与数据获取方法"><a href="#其他查询与数据获取方法" class="headerlink" title="其他查询与数据获取方法"></a>其他查询与数据获取方法</h3><p>所用 <code>Query Builder</code> 支持的查询方法,例如 <code>select</code>、<code>selectSub</code>、<code>whereDate</code>、<code>whereBetween</code> 等等,都可以直接对 <code>Eloquent Model</code> 直接使用,程序会通过魔术方法调用 <code>Query Builder</code> 的相关方法:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> $passthru = [</div><div class="line"> <span class="string">'insert'</span>, <span class="string">'insertGetId'</span>, <span class="string">'getBindings'</span>, <span class="string">'toSql'</span>,</div><div class="line"> <span class="string">'exists'</span>, <span class="string">'count'</span>, <span class="string">'min'</span>, <span class="string">'max'</span>, <span class="string">'avg'</span>, <span class="string">'sum'</span>, <span class="string">'getConnection'</span>,</div><div class="line">];</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__call</span><span class="params">($method, $parameters)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> ...</div><div class="line"> </div><div class="line"> <span class="keyword">if</span> (in_array($method, <span class="keyword">$this</span>->passthru)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->toBase()->{$method}(...$parameters);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->query->{$method}(...$parameters);</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>passthru</code> 中的各个函数在调用前需要加载查询作用域,原因是这些操作基本上是 <code>aggregate</code> 的,需要添加搜索条件才能更加符合预期:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">toBase</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->applyScopes()->getQuery();</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="添加和更新模型"><a href="#添加和更新模型" class="headerlink" title="添加和更新模型"></a>添加和更新模型</h2><h3 id="save-函数"><a href="#save-函数" class="headerlink" title="save 函数"></a>save 函数</h3><p>在 <code>Eloquent Model</code> 中,添加与更新模型可以统一用 <code>save</code> 函数。在添加模型的时候需要事先为 <code>model</code> 属性赋值,可以单个手动赋值,也可以批量赋值。在更新模型的时候,需要事先从数据库中取出模型,然后修改模型属性,最后执行 <code>save</code> 更新操作。官方文档:<a href="https://d.laravel-china.org/docs/5.5/eloquent#inserting-and-updating-models" target="_blank" rel="external">添加和更新模型</a> </p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">save</span><span class="params">(array $options = [])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $query = <span class="keyword">$this</span>->newQueryWithoutScopes();</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->fireModelEvent(<span class="string">'saving'</span>) === <span class="keyword">false</span>) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->exists) {</div><div class="line"> $saved = <span class="keyword">$this</span>->isDirty() ?</div><div class="line"> <span class="keyword">$this</span>->performUpdate($query) : <span class="keyword">true</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">else</span> {</div><div class="line"> $saved = <span class="keyword">$this</span>->performInsert($query);</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (! <span class="keyword">$this</span>->getConnectionName() &&</div><div class="line"> $connection = $query->getConnection()) {</div><div class="line"> <span class="keyword">$this</span>->setConnection($connection->getName());</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> ($saved) {</div><div class="line"> <span class="keyword">$this</span>->finishSave($options);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $saved;</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>save</code> 函数不会加载全局作用域,原因是凡是利用 <code>save</code> 函数进行的插入或者更新的操作都不会存在 <code>where</code> 条件,仅仅利用自身的主键属性来进行更新。如果需要 <code>where</code> 条件可以使用 <code>query\builder</code> 的 <code>update</code> 函数,我们在下面会详细介绍:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">newQueryWithoutScopes</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $builder = <span class="keyword">$this</span>->newEloquentBuilder(<span class="keyword">$this</span>->newBaseQueryBuilder());</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $builder->setModel(<span class="keyword">$this</span>)</div><div class="line"> ->with(<span class="keyword">$this</span>->with)</div><div class="line"> ->withCount(<span class="keyword">$this</span>->withCount);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">newBaseQueryBuilder</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $connection = <span class="keyword">$this</span>->getConnection();</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> QueryBuilder(</div><div class="line"> $connection, $connection->getQueryGrammar(), $connection->getPostProcessor()</div><div class="line"> );</div><div class="line">}</div></pre></td></tr></table></figure>
<p>newQueryWithoutScopes 函数创建新的没有任何其他条件的 <code>Eloquent\builder</code> 类,而 <code>Eloquent\builder</code> 类需要 <code>Query\builder</code> 类作为底层查询构造器。</p>
<h3 id="performUpdate-函数"><a href="#performUpdate-函数" class="headerlink" title="performUpdate 函数"></a>performUpdate 函数</h3><p>如果当前的数据库模型对象是从数据库中取出的,也就是直接或间接的调用 <code>get()</code> 函数从数据库中获取到的数据库对象,那么其 <code>exists</code> 必然是 <code>true</code></p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">isDirty</span><span class="params">($attributes = null)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->hasChanges(</div><div class="line"> <span class="keyword">$this</span>->getDirty(), is_array($attributes) ? $attributes : func_get_args()</div><div class="line"> );</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getDirty</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $dirty = [];</div><div class="line"></div><div class="line"> <span class="keyword">foreach</span> (<span class="keyword">$this</span>->getAttributes() <span class="keyword">as</span> $key => $value) {</div><div class="line"> <span class="keyword">if</span> (! <span class="keyword">$this</span>->originalIsEquivalent($key, $value)) {</div><div class="line"> $dirty[$key] = $value;</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $dirty;</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>getDirty</code> 函数可以获取所有与原始值不同的属性值,也就是需要更新的数据库字段。关键函数在于 <code>originalIsEquivalent</code>:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">originalIsEquivalent</span><span class="params">($key, $current)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (! array_key_exists($key, <span class="keyword">$this</span>->original)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> $original = <span class="keyword">$this</span>->getOriginal($key);</div><div class="line"></div><div class="line"> <span class="keyword">if</span> ($current === $original) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line"> } <span class="keyword">elseif</span> (is_null($current)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> } <span class="keyword">elseif</span> (<span class="keyword">$this</span>->isDateAttribute($key)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->fromDateTime($current) ===</div><div class="line"> <span class="keyword">$this</span>->fromDateTime($original);</div><div class="line"> } <span class="keyword">elseif</span> (<span class="keyword">$this</span>->hasCast($key)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->castAttribute($key, $current) ===</div><div class="line"> <span class="keyword">$this</span>->castAttribute($key, $original);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> is_numeric($current) && is_numeric($original)</div><div class="line"> && strcmp((string) $current, (string) $original) === <span class="number">0</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>可以看到,对于数据库可以转化的属性都要先进行转化,然后再开始对比。比较出的结果,就是我们需要 <code>update</code> 的字段。</p>
<p>执行更新的时候,除了 <code>getDirty</code> 函数获得的待更新字段,还会有 <code>UPDATED_AT</code> 这个字段:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">performUpdate</span><span class="params">(Builder $query)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->fireModelEvent(<span class="string">'updating'</span>) === <span class="keyword">false</span>) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->usesTimestamps()) {</div><div class="line"> <span class="keyword">$this</span>->updateTimestamps();</div><div class="line"> }</div><div class="line"></div><div class="line"> $dirty = <span class="keyword">$this</span>->getDirty();</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (count($dirty) > <span class="number">0</span>) {</div><div class="line"> <span class="keyword">$this</span>->setKeysForSaveQuery($query)->update($dirty);</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->fireModelEvent(<span class="string">'updated'</span>, <span class="keyword">false</span>);</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->syncChanges();</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">updateTimestamps</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $time = <span class="keyword">$this</span>->freshTimestamp();</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (! is_null(<span class="keyword">static</span>::UPDATED_AT) && ! <span class="keyword">$this</span>->isDirty(<span class="keyword">static</span>::UPDATED_AT)) {</div><div class="line"> <span class="keyword">$this</span>->setUpdatedAt($time);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (! <span class="keyword">$this</span>->exists && ! <span class="keyword">$this</span>->isDirty(<span class="keyword">static</span>::CREATED_AT)) {</div><div class="line"> <span class="keyword">$this</span>->setCreatedAt($time);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>执行更新的时候,<code>where</code> 条件只有一个,那就是主键 <code>id</code>:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">setKeysForSaveQuery</span><span class="params">(Builder $query)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $query->where(<span class="keyword">$this</span>->getKeyName(), <span class="string">'='</span>, <span class="keyword">$this</span>->getKeyForSaveQuery());</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $query;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getKeyForSaveQuery</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->original[<span class="keyword">$this</span>->getKeyName()]</div><div class="line"> ?? <span class="keyword">$this</span>->getKey();</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getKey</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->getAttribute(<span class="keyword">$this</span>->getKeyName());</div><div class="line">}</div></pre></td></tr></table></figure>
<p>最后会调用 <code>EloquentBuilder</code> 的 <code>update</code> 函数:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">update</span><span class="params">(array $values)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->toBase()->update(<span class="keyword">$this</span>->addUpdatedAtColumn($values));</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">addUpdatedAtColumn</span><span class="params">(array $values)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (! <span class="keyword">$this</span>->model->usesTimestamps()) {</div><div class="line"> <span class="keyword">return</span> $values;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> Arr::add(</div><div class="line"> $values, <span class="keyword">$this</span>->model->getUpdatedAtColumn(),</div><div class="line"> <span class="keyword">$this</span>->model->freshTimestampString()</div><div class="line"> );</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">freshTimestampString</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->fromDateTime(<span class="keyword">$this</span>->freshTimestamp());</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">fromDateTime</span><span class="params">($value)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> is_null($value) ? $value : <span class="keyword">$this</span>->asDateTime($value)->format(</div><div class="line"> <span class="keyword">$this</span>->getDateFormat()</div><div class="line"> );</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="performInsert"><a href="#performInsert" class="headerlink" title="performInsert"></a>performInsert</h3><p>关于数据库对象的插入,如果数据库的主键被设置为 <code>increment</code>,也就是自增的话,程序会调用 <code>insertAndSetId</code>,这个时候不需要给数据库模型对象手动赋值主键 <code>id</code>。若果数据库的主键并不支持自增,那么就需要在插入前,为数据库对象的主键 <code>id</code> 赋值,否则数据库会报错。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">performInsert</span><span class="params">(Builder $query)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->fireModelEvent(<span class="string">'creating'</span>) === <span class="keyword">false</span>) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->usesTimestamps()) {</div><div class="line"> <span class="keyword">$this</span>->updateTimestamps();</div><div class="line"> }</div><div class="line"></div><div class="line"> $attributes = <span class="keyword">$this</span>->attributes;</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->getIncrementing()) {</div><div class="line"> <span class="keyword">$this</span>->insertAndSetId($query, $attributes);</div><div class="line"> }</div><div class="line"> <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">empty</span>($attributes)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> $query->insert($attributes);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->exists = <span class="keyword">true</span>;</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->wasRecentlyCreated = <span class="keyword">true</span>;</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->fireModelEvent(<span class="string">'created'</span>, <span class="keyword">false</span>);</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>laravel</code> 默认数据库的主键支持自增属性,程序调用的也是函数 <code>insertAndSetId</code> 函数:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">insertAndSetId</span><span class="params">(Builder $query, $attributes)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $id = $query->insertGetId($attributes, $keyName = <span class="keyword">$this</span>->getKeyName());</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->setAttribute($keyName, $id);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>插入后,会将插入后得到的主键 <code>id</code> 返回,并赋值到模型的属性当中。</p>
<p>如果数据库主键不支持自增,那么我们在数据库类中要设置:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> $incrementing = <span class="keyword">false</span>;</div></pre></td></tr></table></figure>
<p>每次进行插入数据的时候,需要手动给主键赋值。</p>
<h3 id="update-函数"><a href="#update-函数" class="headerlink" title="update 函数"></a>update 函数</h3><p><code>save</code> 函数仅仅支持手动的属性赋值,无法批量赋值。<code>laravel</code> 的 <code>Eloquent Model</code> 还有一个函数: <code>update</code> 支持批量属性赋值。有意思的是,<code>Eloquent Builder</code> 也有函数 <code>update</code>,那个是上一小节提到的 <code>performUpdate</code> 所调用的函数。</p>
<p>两个 <code>update</code> 功能一致,只是 <code>Model</code> 的 <code>update</code> 函数比较适用于更新从数据库取回的数据库对象:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">$flight = App\Flight::find(<span class="number">1</span>);</div><div class="line"></div><div class="line">$flight->update([<span class="string">'name'</span> => <span class="string">'New Flight Name'</span>,<span class="string">'desc'</span> => <span class="string">'test'</span>]);</div></pre></td></tr></table></figure>
<p>而 <code>Builder</code> 的 <code>update</code> 适用于多查询条件下的更新:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">App\Flight::where(<span class="string">'active'</span>, <span class="number">1</span>)</div><div class="line"> ->where(<span class="string">'destination'</span>, <span class="string">'San Diego'</span>)</div><div class="line"> ->update([<span class="string">'delayed'</span> => <span class="number">1</span>]);</div></pre></td></tr></table></figure>
<p>无论哪一种,都会自动更新 <code>updated_at</code> 字段。</p>
<p><code>Model</code> 的 <code>update</code> 函数借助 <code>fill</code> 函数与 <code>save</code> 函数:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">update</span><span class="params">(array $attributes = [], array $options = [])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (! <span class="keyword">$this</span>->exists) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->fill($attributes)->save($options);</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="make-函数"><a href="#make-函数" class="headerlink" title="make 函数"></a>make 函数</h3><p>同样的,<code>save</code> 的插入也仅仅支持手动属性赋值,如果想实现批量属性赋值的插入可以使用 <code>make</code> 函数:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">$model = App\Flight::make([<span class="string">'name'</span> => <span class="string">'New Flight Name'</span>,<span class="string">'desc'</span> => <span class="string">'test'</span>]);</div><div class="line"></div><div class="line">$model->save();</div></pre></td></tr></table></figure>
<p><code>make</code> 函数实际上仅仅是新建了一个 <code>Eloquent Model</code>,并批量赋予属性值:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">make</span><span class="params">(array $attributes = [])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->newModelInstance($attributes);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">newModelInstance</span><span class="params">($attributes = [])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->model->newInstance($attributes)->setConnection(</div><div class="line"> <span class="keyword">$this</span>->query->getConnection()->getName()</div><div class="line"> );</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="create-函数"><a href="#create-函数" class="headerlink" title="create 函数"></a>create 函数</h3><p>如果想要一步到位,批量赋值属性与插入一起操作,可以使用 <code>create</code> 函数:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">App\Flight::create([<span class="string">'name'</span> => <span class="string">'New Flight Name'</span>,<span class="string">'desc'</span> => <span class="string">'test'</span>]);</div></pre></td></tr></table></figure>
<p>相比较 <code>make</code> 函数,<code>create</code> 函数更进一步调用了 <code>save</code> 函数:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">create</span><span class="params">(array $attributes = [])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> tap(<span class="keyword">$this</span>->newModelInstance($attributes), <span class="function"><span class="keyword">function</span> <span class="params">($instance)</span> </span>{</div><div class="line"> $instance->save();</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<p>实际上,属性值是否可以批量赋值需要受 <code>fillable</code> 或 <code>guarded</code> 来控制,如果我们想要强制批量赋值可以使用 <code>forceCreate</code>:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">forceCreate</span><span class="params">(array $attributes)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->model->unguarded(<span class="function"><span class="keyword">function</span> <span class="params">()</span> <span class="title">use</span> <span class="params">($attributes)</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->newModelInstance()->create($attributes);</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="findOrNew-函数"><a href="#findOrNew-函数" class="headerlink" title="findOrNew 函数"></a>findOrNew 函数</h3><p><code>laravel</code> 提供一种主键查询或者新建数据库对象的函数:<code>findOrNew</code>:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">findOrNew</span><span class="params">($id, $columns = [<span class="string">'*'</span>])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (! is_null($model = <span class="keyword">$this</span>->find($id, $columns))) {</div><div class="line"> <span class="keyword">return</span> $model;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->newModelInstance();</div><div class="line">}</div></pre></td></tr></table></figure>
<p>值得注意的是,当查询失败的时候,会返回一个全新的数据库对象,不含有任何 <code>attributes</code>。</p>
<h3 id="firstOrNew-函数"><a href="#firstOrNew-函数" class="headerlink" title="firstOrNew 函数"></a>firstOrNew 函数</h3><p><code>laravel</code> 提供一种自定义查询或者新建数据库对象的函数:<code>firstOrNew</code>:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">firstOrNew</span><span class="params">(array $attributes, array $values = [])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (! is_null($instance = <span class="keyword">$this</span>->where($attributes)->first())) {</div><div class="line"> <span class="keyword">return</span> $instance;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->newModelInstance($attributes + $values);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>值得注意的是,如果查询失败,会返回一个含有 <code>attributes</code> 和 <code>values</code> 两者合并的属性的数据库对象。</p>
<h3 id="firstOrCreate-函数"><a href="#firstOrCreate-函数" class="headerlink" title="firstOrCreate 函数"></a>firstOrCreate 函数</h3><p>类似于 <code>firstOrNew</code> 函数,<code>firstOrCreate</code> 函数也用于自定义查询或者新建数据库对象,不同的是,<code>firstOrCreate</code> 函数还进一步对数据进行了插入操作:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">firstOrCreate</span><span class="params">(array $attributes, array $values = [])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (! is_null($instance = <span class="keyword">$this</span>->where($attributes)->first())) {</div><div class="line"> <span class="keyword">return</span> $instance;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> tap(<span class="keyword">$this</span>->newModelInstance($attributes + $values), <span class="function"><span class="keyword">function</span> <span class="params">($instance)</span> </span>{</div><div class="line"> $instance->save();</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="updateOrCreate-函数"><a href="#updateOrCreate-函数" class="headerlink" title="updateOrCreate 函数"></a>updateOrCreate 函数</h3><p>在 <code>firstOrCreate</code> 函数基础上,除了对数据进行查询,还会对查询成功的数据利用 <code>value</code> 进行更新:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">updateOrCreate</span><span class="params">(array $attributes, array $values = [])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> tap(<span class="keyword">$this</span>->firstOrNew($attributes), <span class="function"><span class="keyword">function</span> <span class="params">($instance)</span> <span class="title">use</span> <span class="params">($values)</span> </span>{</div><div class="line"> $instance->fill($values)->save();</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="firstOr-函数"><a href="#firstOr-函数" class="headerlink" title="firstOr 函数"></a>firstOr 函数</h3><p>如果想要自定义查找失败后的操作,可以使用 <code>firstOr</code> 函数,该函数可以传入闭包函数,处理找不到数据的情况:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">firstOr</span><span class="params">($columns = [<span class="string">'*'</span>], Closure $callback = null)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> ($columns <span class="keyword">instanceof</span> Closure) {</div><div class="line"> $callback = $columns;</div><div class="line"></div><div class="line"> $columns = [<span class="string">'*'</span>];</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (! is_null($model = <span class="keyword">$this</span>->first($columns))) {</div><div class="line"> <span class="keyword">return</span> $model;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> call_user_func($callback);</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="删除模型"><a href="#删除模型" class="headerlink" title="删除模型"></a>删除模型</h2><p>删除模型也会分为两种,一种是针对 <code>Eloquent Model</code> 的删除,这种删除必须是从数据库中取出的对象。还有一种是 <code>Eloquent Builder</code> 的删除,这种删除一般会带有多个查询条件。我们这一小节主要讲 <code>model</code> 的删除:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">delete</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (is_null(<span class="keyword">$this</span>->getKeyName())) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="keyword">Exception</span>(<span class="string">'No primary key defined on model.'</span>);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (! <span class="keyword">$this</span>->exists) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->fireModelEvent(<span class="string">'deleting'</span>) === <span class="keyword">false</span>) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->touchOwners();</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->performDeleteOnModel();</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->fireModelEvent(<span class="string">'deleted'</span>, <span class="keyword">false</span>);</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>删除模型时,模型对象必然要有主键。<code>performDeleteOnModel</code> 函数执行具体的删除操作:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">performDeleteOnModel</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">$this</span>->setKeysForSaveQuery(<span class="keyword">$this</span>->newQueryWithoutScopes())->delete();</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->exists = <span class="keyword">false</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">setKeysForSaveQuery</span><span class="params">(Builder $query)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $query->where(<span class="keyword">$this</span>->getKeyName(), <span class="string">'='</span>, <span class="keyword">$this</span>->getKeyForSaveQuery());</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $query;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>所以实际上,<code>Model</code> 调用的也是 <code>builder</code> 的 <code>delete</code> 函数。</p>
<h3 id="软删除"><a href="#软删除" class="headerlink" title="软删除"></a>软删除</h3><p>如果想要使用软删除,需要使用 <code>Illuminate\Database\Eloquent\SoftDeletes</code> 这个 trait。并且需要定义软删除字段,默认为 <code>deleted_at</code>,将软删除字段放入 <code>dates</code> 中,具体用法可参考官方文档:<a href="https://d.laravel-china.org/docs/5.5/eloquent#soft-deleting" target="_blank" rel="external">软删除</a></p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Flight</span> <span class="keyword">extends</span> <span class="title">Model</span></span></div><div class="line"><span class="class"></span>{</div><div class="line"> <span class="keyword">use</span> <span class="title">SoftDeletes</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 需要被转换成日期的属性。</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@var</span> array</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">protected</span> $dates = [<span class="string">'deleted_at'</span>];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>我们先看看这个 <code>trait</code>:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">trait</span> SoftDeletes </div><div class="line">{</div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">bootSoftDeletes</span><span class="params">()</span></span></div><div class="line"><span class="function"> </span>{</div><div class="line"> <span class="keyword">static</span>::addGlobalScope(<span class="keyword">new</span> SoftDeletingScope);</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果使用了软删除,在 <code>model</code> 的启动过程中,就会启动软删除的这个函数。可以看出来,软删除是需要查询作用域来合作发挥作用的。我们看看这个 <code>SoftDeletingScope</code> :</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">SoftDeletingScope</span> <span class="keyword">implements</span> <span class="title">Scope</span></span></div><div class="line"><span class="class"></span>{</div><div class="line"> <span class="keyword">protected</span> $extensions = [<span class="string">'Restore'</span>, <span class="string">'WithTrashed'</span>, <span class="string">'WithoutTrashed'</span>, <span class="string">'OnlyTrashed'</span>];</div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">apply</span><span class="params">(Builder $builder, Model $model)</span></span></div><div class="line"><span class="function"> </span>{</div><div class="line"> $builder->whereNull($model->getQualifiedDeletedAtColumn());</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">extend</span><span class="params">(Builder $builder)</span></span></div><div class="line"><span class="function"> </span>{</div><div class="line"> <span class="keyword">foreach</span> (<span class="keyword">$this</span>->extensions <span class="keyword">as</span> $extension) {</div><div class="line"> <span class="keyword">$this</span>->{<span class="string">"add{$extension}"</span>}($builder);</div><div class="line"> }</div><div class="line"></div><div class="line"> $builder->onDelete(<span class="function"><span class="keyword">function</span> <span class="params">(Builder $builder)</span> </span>{</div><div class="line"> $column = <span class="keyword">$this</span>->getDeletedAtColumn($builder);</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $builder->update([</div><div class="line"> $column => $builder->getModel()->freshTimestampString(),</div><div class="line"> ]);</div><div class="line"> });</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>apply</code> 函数是加载全局域调用的函数,每次进行查询的时候,调用 <code>get</code> 函数就会自动加载这个函数,<code>whereNull</code> 这个查询条件会被加载到具体的 <code>where</code> 条件中。<code>deleted_at</code> 字段一般被设置为 <code>null</code>,在执行软删除的时候,该字段会被赋予时间格式的值,标志着被删除的时间。</p>
<p>在加载全局作用域的时候,还会调用 <code>extend</code> 函数,<code>extend</code> 函数为 <code>model</code> 添加了四个函数:</p>
<ul>
<li>WithTrashed</li>
</ul>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"> <span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">addWithTrashed</span><span class="params">(Builder $builder)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $builder->macro(<span class="string">'withTrashed'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(Builder $builder)</span> </span>{</div><div class="line"> <span class="keyword">return</span> $builder->withoutGlobalScope(<span class="keyword">$this</span>);</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>withTrashed</code> 函数取消了软删除的全局作用域,这样我们查询数据的时候就会查询到正常数据和被软删除的数据。</p>
<ul>
<li>withoutTrashed</li>
</ul>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">addWithoutTrashed</span><span class="params">(Builder $builder)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $builder->macro(<span class="string">'withoutTrashed'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(Builder $builder)</span> </span>{</div><div class="line"> $model = $builder->getModel();</div><div class="line"></div><div class="line"> $builder->withoutGlobalScope(<span class="keyword">$this</span>)->whereNull(</div><div class="line"> $model->getQualifiedDeletedAtColumn()</div><div class="line"> );</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $builder;</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>withTrashed</code> 函数着重强调了不要获取软删除的数据。</p>
<ul>
<li>onlyTrashed</li>
</ul>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">addOnlyTrashed</span><span class="params">(Builder $builder)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $builder->macro(<span class="string">'onlyTrashed'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(Builder $builder)</span> </span>{</div><div class="line"> $model = $builder->getModel();</div><div class="line"></div><div class="line"> $builder->withoutGlobalScope(<span class="keyword">$this</span>)->whereNotNull(</div><div class="line"> $model->getQualifiedDeletedAtColumn()</div><div class="line"> );</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $builder;</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果只想获取被软删除的数据,可以使用这个函数 <code>onlyTrashed</code>,可以看到,它使用了 <code>whereNotNull</code>。</p>
<ul>
<li>restore</li>
</ul>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">addRestore</span><span class="params">(Builder $builder)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $builder->macro(<span class="string">'restore'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(Builder $builder)</span> </span>{</div><div class="line"> $builder->withTrashed();</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $builder->update([$builder->getModel()->getDeletedAtColumn() => <span class="keyword">null</span>]);</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果想要恢复被删除的数据,还可以使用 <code>restore</code>,重新将 <code>deleted_at</code> 数据恢复为 null。</p>
<h3 id="performDeleteOnModel"><a href="#performDeleteOnModel" class="headerlink" title="performDeleteOnModel"></a>performDeleteOnModel</h3><p>SoftDeletes 这个 trait 会重载 <code>performDeleteOnModel</code> 函数,它将不会调用 <code>Eloquent Builder</code> 的 <code>delete</code> 方法,而是采用更新操作:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">performDeleteOnModel</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->forceDeleting) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->newQueryWithoutScopes()->where(<span class="keyword">$this</span>->getKeyName(), <span class="keyword">$this</span>->getKey())->forceDelete();</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->runSoftDelete();</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">runSoftDelete</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $query = <span class="keyword">$this</span>->newQueryWithoutScopes()->where(<span class="keyword">$this</span>->getKeyName(), <span class="keyword">$this</span>->getKey());</div><div class="line"></div><div class="line"> $time = <span class="keyword">$this</span>->freshTimestamp();</div><div class="line"></div><div class="line"> $columns = [<span class="keyword">$this</span>->getDeletedAtColumn() => <span class="keyword">$this</span>->fromDateTime($time)];</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->{<span class="keyword">$this</span>->getDeletedAtColumn()} = $time;</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->timestamps) {</div><div class="line"> <span class="keyword">$this</span>->{<span class="keyword">$this</span>->getUpdatedAtColumn()} = $time;</div><div class="line"></div><div class="line"> $columns[<span class="keyword">$this</span>->getUpdatedAtColumn()] = <span class="keyword">$this</span>->fromDateTime($time);</div><div class="line"> }</div><div class="line"></div><div class="line"> $query->update($columns);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>删除操作不仅更新了 <code>deleted_at</code>,还更新了 <code>updated_at</code> 字段。</p>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<link itemprop="mainEntityOfPage" href="https://leoyang90.github.io/2017/10/05/Laravel Database——Eloquent Model 源码分析(上)/">
<span style="display:none" itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Leo Yang">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/master.jpg">
</span>
<span style="display:none" itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="LEOYANG'S BLOG">
<span style="display:none" itemprop="logo" itemscope itemtype="http://schema.org/ImageObject">
<img style="display:none;" itemprop="url image" alt="LEOYANG'S BLOG" src="">
</span>
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2017/10/05/Laravel Database——Eloquent Model 源码分析(上)/" itemprop="url">
Laravel Database——Eloquent Model 源码分析(上)
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2017-10-05T23:25:00+08:00">
2017-10-05
</time>
</span>
<span class="post-category" >
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/php/" itemprop="url" rel="index">
<span itemprop="name">php</span>
</a>
</span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/php/database/" itemprop="url" rel="index">
<span itemprop="name">database</span>
</a>
</span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/php/database/laravel/" itemprop="url" rel="index">
<span itemprop="name">laravel</span>
</a>
</span>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<a href="/2017/10/05/Laravel Database——Eloquent Model 源码分析(上)/#comments" itemprop="discussionUrl">
<span class="post-comments-count ds-thread-count" data-thread-key="2017/10/05/Laravel Database——Eloquent Model 源码分析(上)/" itemprop="commentCount"></span>
</a>
</span>
<span id="/2017/10/05/Laravel Database——Eloquent Model 源码分析(上)/" class="leancloud_visitors" data-flag-title="Laravel Database——Eloquent Model 源码分析(上)">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-eye"></i>
</span>
<span class="post-meta-item-text">阅读次数 </span>
<span class="leancloud-visitors-count"></span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<hr>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>前面几个博客向大家介绍了查询构造器的原理与源码,然而查询构造器更多是为 <code>Eloquent Model</code> 服务的,我们对数据库操作更加方便的是使用 <code>Eloquent Model</code>。 本篇文章将会大家介绍 <code>Model</code> 的一些特性原理。</p>
<h2 id="Eloquent-Model-修改器"><a href="#Eloquent-Model-修改器" class="headerlink" title="Eloquent Model 修改器"></a>Eloquent Model 修改器</h2><p>当我们在 <code>Eloquent</code> 模型实例中设置某些属性值的时候,修改器允许对 <code>Eloquent</code> 属性值进行格式化。如果对修改器不熟悉,请参考官方文档:<a href="https://d.laravel-china.org/docs/5.5/eloquent-mutators" target="_blank" rel="external">Eloquent: 修改器</a></p>
<p>下面先看看修改器的原理:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">offsetSet</span><span class="params">($offset, $value)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">$this</span>->setAttribute($offset, $value);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">setAttribute</span><span class="params">($key, $value)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->hasSetMutator($key)) {</div><div class="line"> $method = <span class="string">'set'</span>.Str::studly($key).<span class="string">'Attribute'</span>;</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->{$method}($value);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">elseif</span> ($value && <span class="keyword">$this</span>->isDateAttribute($key)) {</div><div class="line"> $value = <span class="keyword">$this</span>->fromDateTime($value);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->isJsonCastable($key) && ! is_null($value)) {</div><div class="line"> $value = <span class="keyword">$this</span>->castAttributeAsJson($key, $value);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (Str::contains($key, <span class="string">'->'</span>)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->fillJsonAttribute($key, $value);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->attributes[$key] = $value;</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="自定义修改器"><a href="#自定义修改器" class="headerlink" title="自定义修改器"></a>自定义修改器</h3><p>当我们为 <code>model</code> 的成员变量赋值的时候,就会调用 <code>offsetSet</code> 函数,进而运行 <code>setAttribute</code> 函数,在这个函数中第一个检查的就是是否存在预处理函数:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">hasSetMutator</span><span class="params">($key)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> method_exists(<span class="keyword">$this</span>, <span class="string">'set'</span>.Str::studly($key).<span class="string">'Attribute'</span>);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果存在该函数,就会直接调用自定义修改器。</p>
<h3 id="日期转换器"><a href="#日期转换器" class="headerlink" title="日期转换器"></a>日期转换器</h3><p>接着如果没有自定义修改器的话,还会检查当前更新的成员变量是否是日期属性:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">isDateAttribute</span><span class="params">($key)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> in_array($key, <span class="keyword">$this</span>->getDates()) ||</div><div class="line"> <span class="keyword">$this</span>->isDateCastable($key);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getDates</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $defaults = [<span class="keyword">static</span>::CREATED_AT, <span class="keyword">static</span>::UPDATED_AT];</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->usesTimestamps()</div><div class="line"> ? array_unique(array_merge(<span class="keyword">$this</span>->dates, $defaults))</div><div class="line"> : <span class="keyword">$this</span>->dates;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">isDateCastable</span><span class="params">($key)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->hasCast($key, [<span class="string">'date'</span>, <span class="string">'datetime'</span>]);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>字段的时间属性有两种设置方法,一种是设置 <code>$dates</code> 属性:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> $dates = [<span class="string">'date_attr'</span>];</div></pre></td></tr></table></figure>
<p>还有一种方法是设置 <code>cast</code> 数组:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> $casts = [<span class="string">'date_attr'</span> => <span class="string">'date'</span>];</div></pre></td></tr></table></figure>
<p>只要是时间属性的字段,无论是什么类型的值,<code>laravel</code> 都会自动将其转化为数据库的时间格式。数据库的时间格式设置是 <code>dateFormat</code> 成员变量,不设置的时候,默认的时间格式为 `Y-m-d H:i:s’:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> $dateFormat = [<span class="string">'U'</span>];</div><div class="line"></div><div class="line"><span class="keyword">protected</span> $dateFormat = [<span class="string">'Y-m-d H:i:s'</span>];</div></pre></td></tr></table></figure>
<p>当数据库对应的字段是时间类型时,为其赋值就可以非常灵活。我们可以赋值 <code>Carbon</code> 类型、<code>DateTime</code> 类型、数字类型、字符串等等:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">fromDateTime</span><span class="params">($value)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> is_null($value) ? $value : <span class="keyword">$this</span>->asDateTime($value)->format(</div><div class="line"> <span class="keyword">$this</span>->getDateFormat()</div><div class="line"> );</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">asDateTime</span><span class="params">($value)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> ($value <span class="keyword">instanceof</span> Carbon) {</div><div class="line"> <span class="keyword">return</span> $value;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> ($value <span class="keyword">instanceof</span> DateTimeInterface) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Carbon(</div><div class="line"> $value->format(<span class="string">'Y-m-d H:i:s.u'</span>), $value->getTimezone()</div><div class="line"> );</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (is_numeric($value)) {</div><div class="line"> <span class="keyword">return</span> Carbon::createFromTimestamp($value);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->isStandardDateFormat($value)) {</div><div class="line"> <span class="keyword">return</span> Carbon::createFromFormat(<span class="string">'Y-m-d'</span>, $value)->startOfDay();</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> Carbon::createFromFormat(</div><div class="line"> <span class="keyword">$this</span>->getDateFormat(), $value</div><div class="line"> );</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="json-转换器"><a href="#json-转换器" class="headerlink" title="json 转换器"></a>json 转换器</h3><p>接下来,如果该变量被设置为 <code>array</code>、<code>json</code> 等属性,那么其将会转化为 <code>json</code> 类型。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">isJsonCastable</span><span class="params">($key)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->hasCast($key, [<span class="string">'array'</span>, <span class="string">'json'</span>, <span class="string">'object'</span>, <span class="string">'collection'</span>]);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">asJson</span><span class="params">($value)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> json_encode($value);</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="Eloquent-Model-访问器"><a href="#Eloquent-Model-访问器" class="headerlink" title="Eloquent Model 访问器"></a>Eloquent Model 访问器</h2><p>相比较修改器来说,访问器的适用情景会更加多。例如,我们经常把一些关于类型的字段设置为 <code>1</code>、<code>2</code>、<code>3</code> 等等,例如用户数据表中用户性别字段,<code>1</code> 代表男,<code>2</code> 代表女,很多时候我们取出这些值之后必然要经过转换,然后再显示出来。这时候就需要定义访问器。</p>
<p>访问器的源码:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getAttribute</span><span class="params">($key)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (! $key) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (array_key_exists($key, <span class="keyword">$this</span>->attributes) ||</div><div class="line"> <span class="keyword">$this</span>->hasGetMutator($key)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->getAttributeValue($key);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (method_exists(<span class="keyword">self</span>::class, $key)) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->getRelationValue($key);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>可以看到,当我们访问数据库对象的成员变量的时候,大致可以分为两类:属性值与关系对象。关系对象我们以后再详细来说,本文中先说关于属性的访问。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getAttributeValue</span><span class="params">($key)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $value = <span class="keyword">$this</span>->getAttributeFromArray($key);</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->hasGetMutator($key)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->mutateAttribute($key, $value);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->hasCast($key)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->castAttribute($key, $value);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (in_array($key, <span class="keyword">$this</span>->getDates()) &&</div><div class="line"> ! is_null($value)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->asDateTime($value);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $value;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>与修改器类似,访问器也由三部分构成:自定义访问器、日期访问器、类型访问器。</p>
<h3 id="获取原始值"><a href="#获取原始值" class="headerlink" title="获取原始值"></a>获取原始值</h3><p>访问器的第一步就是从成员变量 <code>attributes</code> 中获取原始的字段值,一般指的是存在数据库的值。有的时候,我们要取的属性并不在 <code>attributes</code> 中,这时候就会返回 <code>null</code>。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getAttributeFromArray</span><span class="params">($key)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">isset</span>(<span class="keyword">$this</span>->attributes[$key])) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->attributes[$key];</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="自定义访问器"><a href="#自定义访问器" class="headerlink" title="自定义访问器"></a>自定义访问器</h3><p>如果定义了访问器,那么就会调用访问器,获取返回值:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">hasGetMutator</span><span class="params">($key)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> method_exists(<span class="keyword">$this</span>, <span class="string">'get'</span>.Str::studly($key).<span class="string">'Attribute'</span>);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">mutateAttribute</span><span class="params">($key, $value)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->{<span class="string">'get'</span>.Str::studly($key).<span class="string">'Attribute'</span>}($value);</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="类型转换"><a href="#类型转换" class="headerlink" title="类型转换"></a>类型转换</h3><p>若我们在成员变量 <code>$casts</code> 数组中为属性定义了类型转换,那么就要进行类型转换:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">hasCast</span><span class="params">($key, $types = null)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (array_key_exists($key, <span class="keyword">$this</span>->getCasts())) {</div><div class="line"> <span class="keyword">return</span> $types ? in_array(<span class="keyword">$this</span>->getCastType($key), (<span class="keyword">array</span>) $types, <span class="keyword">true</span>) : <span class="keyword">true</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">castAttribute</span><span class="params">($key, $value)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (is_null($value)) {</div><div class="line"> <span class="keyword">return</span> $value;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">switch</span> (<span class="keyword">$this</span>->getCastType($key)) {</div><div class="line"> <span class="keyword">case</span> <span class="string">'int'</span>:</div><div class="line"> <span class="keyword">case</span> <span class="string">'integer'</span>:</div><div class="line"> <span class="keyword">return</span> (int) $value;</div><div class="line"> <span class="keyword">case</span> <span class="string">'real'</span>:</div><div class="line"> <span class="keyword">case</span> <span class="string">'float'</span>:</div><div class="line"> <span class="keyword">case</span> <span class="string">'double'</span>:</div><div class="line"> <span class="keyword">return</span> (float) $value;</div><div class="line"> <span class="keyword">case</span> <span class="string">'string'</span>:</div><div class="line"> <span class="keyword">return</span> (string) $value;</div><div class="line"> <span class="keyword">case</span> <span class="string">'bool'</span>:</div><div class="line"> <span class="keyword">case</span> <span class="string">'boolean'</span>:</div><div class="line"> <span class="keyword">return</span> (bool) $value;</div><div class="line"> <span class="keyword">case</span> <span class="string">'object'</span>:</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->fromJson($value, <span class="keyword">true</span>);</div><div class="line"> <span class="keyword">case</span> <span class="string">'array'</span>:</div><div class="line"> <span class="keyword">case</span> <span class="string">'json'</span>:</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->fromJson($value);</div><div class="line"> <span class="keyword">case</span> <span class="string">'collection'</span>:</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BaseCollection(<span class="keyword">$this</span>->fromJson($value));</div><div class="line"> <span class="keyword">case</span> <span class="string">'date'</span>:</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->asDate($value);</div><div class="line"> <span class="keyword">case</span> <span class="string">'datetime'</span>:</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->asDateTime($value);</div><div class="line"> <span class="keyword">case</span> <span class="string">'timestamp'</span>:</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->asTimestamp($value);</div><div class="line"> <span class="keyword">default</span>:</div><div class="line"> <span class="keyword">return</span> $value;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="日期转换"><a href="#日期转换" class="headerlink" title="日期转换"></a>日期转换</h3><p>若当前属性是 <code>CREATED_AT</code>、<code>UPDATED_AT</code> 或者被存入成员变量 <code>dates</code> 中,那么就要进行日期转换。日期转换函数 <code>asDateTime</code> 可以查看上一节中的内容。</p>
<h2 id="Eloquent-Model-数组转化"><a href="#Eloquent-Model-数组转化" class="headerlink" title="Eloquent Model 数组转化"></a>Eloquent Model 数组转化</h2><p>在使用数据库对象中,我们经常使用 <code>toArray</code> 函数,它可以将从数据库中取出的所有属性和关系模型转化为数组:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">toArray</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> array_merge(<span class="keyword">$this</span>->attributesToArray(), <span class="keyword">$this</span>->relationsToArray());</div><div class="line">}</div><div class="line">``` </div><div class="line"></div><div class="line">本文中只介绍属性转化为数组的部分:</div><div class="line"></div><div class="line">```php</div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">attributesToArray</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $attributes = <span class="keyword">$this</span>->addDateAttributesToArray(</div><div class="line"> $attributes = <span class="keyword">$this</span>->getArrayableAttributes()</div><div class="line"> );</div><div class="line"></div><div class="line"> $attributes = <span class="keyword">$this</span>->addMutatedAttributesToArray(</div><div class="line"> $attributes, $mutatedAttributes = <span class="keyword">$this</span>->getMutatedAttributes()</div><div class="line"> );</div><div class="line"> </div><div class="line"> $attributes = <span class="keyword">$this</span>->addCastAttributesToArray(</div><div class="line"> $attributes, $mutatedAttributes</div><div class="line"> );</div><div class="line"></div><div class="line"> <span class="keyword">foreach</span> (<span class="keyword">$this</span>->getArrayableAppends() <span class="keyword">as</span> $key) {</div><div class="line"> $attributes[$key] = <span class="keyword">$this</span>->mutateAttributeForArray($key, <span class="keyword">null</span>);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $attributes;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>与访问器与修改器类似,需要转为数组的元素有日期类型、自定义访问器、类型转换,我们接下来一个个看:</p>
<h3 id="getArrayableAttributes-原始值获取"><a href="#getArrayableAttributes-原始值获取" class="headerlink" title="getArrayableAttributes 原始值获取"></a>getArrayableAttributes 原始值获取</h3><p>首先我们要从成员变量 <code>attributes</code> 数组中获取原始值:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getArrayableAttributes</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->getArrayableItems(<span class="keyword">$this</span>->attributes);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getArrayableItems</span><span class="params">(array $values)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (count(<span class="keyword">$this</span>->getVisible()) > <span class="number">0</span>) {</div><div class="line"> $values = array_intersect_key($values, array_flip(<span class="keyword">$this</span>->getVisible()));</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (count(<span class="keyword">$this</span>->getHidden()) > <span class="number">0</span>) {</div><div class="line"> $values = array_diff_key($values, array_flip(<span class="keyword">$this</span>->getHidden()));</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $values;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>我们还可以为数据库对象设置可见元素 <code>$visible</code> 与隐藏元素 <code>$hidden</code>,这两个变量会控制 <code>toArray</code> 可转化的元素属性。</p>
<h3 id="日期转换-1"><a href="#日期转换-1" class="headerlink" title="日期转换"></a>日期转换</h3><figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">addDateAttributesToArray</span><span class="params">(array $attributes)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">foreach</span> (<span class="keyword">$this</span>->getDates() <span class="keyword">as</span> $key) {</div><div class="line"> <span class="keyword">if</span> (! <span class="keyword">isset</span>($attributes[$key])) {</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> $attributes[$key] = <span class="keyword">$this</span>->serializeDate(</div><div class="line"> <span class="keyword">$this</span>->asDateTime($attributes[$key])</div><div class="line"> );</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $attributes;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">serializeDate</span><span class="params">(DateTimeInterface $date)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> $date->format(<span class="keyword">$this</span>->getDateFormat());</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="自定义访问器转换"><a href="#自定义访问器转换" class="headerlink" title="自定义访问器转换"></a>自定义访问器转换</h3><p>定义了自定义访问器的属性,会调用访问器函数来覆盖原有的属性值,首先我们需要获取所有的自定义访问器变量:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getMutatedAttributes</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $class = <span class="keyword">static</span>::class;</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (! <span class="keyword">isset</span>(<span class="keyword">static</span>::$mutatorCache[$class])) {</div><div class="line"> <span class="keyword">static</span>::cacheMutatedAttributes($class);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">static</span>::$mutatorCache[$class];</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">cacheMutatedAttributes</span><span class="params">($class)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">static</span>::$mutatorCache[$class] = collect(<span class="keyword">static</span>::getMutatorMethods($class))->map(<span class="function"><span class="keyword">function</span> <span class="params">($match)</span> </span>{</div><div class="line"> <span class="keyword">return</span> lcfirst(<span class="keyword">static</span>::$snakeAttributes ? Str::snake($match) : $match);</div><div class="line"> })->all();</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">getMutatorMethods</span><span class="params">($class)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> preg_match_all(<span class="string">'/(?<=^|;)get([^;]+?)Attribute(;|$)/'</span>, implode(<span class="string">';'</span>, get_class_methods($class)), $matches);</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $matches[<span class="number">1</span>];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>可以看到,函数用 <code>get_class_methods</code> 获取类内所有的函数,并筛选出符合 <code>get...Attribute</code> 的函数,获得自定义的访问器变量,并缓存到 <code>mutatorCache</code> 中。</p>
<p>接着将会利用自定义访问器变量替换原始值:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">addMutatedAttributesToArray</span><span class="params">(array $attributes, array $mutatedAttributes)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">foreach</span> ($mutatedAttributes <span class="keyword">as</span> $key) {</div><div class="line"> <span class="keyword">if</span> (! array_key_exists($key, $attributes)) {</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> $attributes[$key] = <span class="keyword">$this</span>->mutateAttributeForArray(</div><div class="line"> $key, $attributes[$key]</div><div class="line"> );</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $attributes;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">mutateAttributeForArray</span><span class="params">($key, $value)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $value = <span class="keyword">$this</span>->mutateAttribute($key, $value);</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $value <span class="keyword">instanceof</span> Arrayable ? $value->toArray() : $value;</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="cast-类型转换"><a href="#cast-类型转换" class="headerlink" title="cast 类型转换"></a>cast 类型转换</h3><p>被定义在 <code>cast</code> 数组中的变量也要进行数组转换,调用的方法和访问器相同,也是 <code>castAttribute</code>,如果是时间类型,还要按照时间格式来转换:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">addCastAttributesToArray</span><span class="params">(array $attributes, array $mutatedAttributes)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">foreach</span> (<span class="keyword">$this</span>->getCasts() <span class="keyword">as</span> $key => $value) {</div><div class="line"> <span class="keyword">if</span> (! array_key_exists($key, $attributes) || in_array($key, $mutatedAttributes)) {</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> $attributes[$key] = <span class="keyword">$this</span>->castAttribute(</div><div class="line"> $key, $attributes[$key]</div><div class="line"> );</div><div class="line"></div><div class="line"> <span class="keyword">if</span> ($attributes[$key] &&</div><div class="line"> ($value === <span class="string">'date'</span> || $value === <span class="string">'datetime'</span>)) {</div><div class="line"> $attributes[$key] = <span class="keyword">$this</span>->serializeDate($attributes[$key]);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $attributes;</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="appends-额外属性添加"><a href="#appends-额外属性添加" class="headerlink" title="appends 额外属性添加"></a>appends 额外属性添加</h3><p><code>toArray()</code> 还会将我们定义在 <code>appends</code> 变量中的属性一起进行数组转换,但是注意被放入 <code>appends</code> 成员变量数组中的属性需要有自定义访问器函数:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getArrayableAppends</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (! count(<span class="keyword">$this</span>->appends)) {</div><div class="line"> <span class="keyword">return</span> [];</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->getArrayableItems(</div><div class="line"> array_combine(<span class="keyword">$this</span>->appends, <span class="keyword">$this</span>->appends)</div><div class="line"> );</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="查询作用域"><a href="#查询作用域" class="headerlink" title="查询作用域"></a>查询作用域</h2><p>查询作用域分为全局作用域与本地作用域。全局作用域不需要手动调用,由程序在每次的查询中自动加载,本地作用域需要在查询的时候进行手动调用。官方文档:<a href="https://d.laravel-china.org/docs/5.5/eloquent#query-scopes" target="_blank" rel="external">查询作用域</a></p>
<h3 id="全局作用域"><a href="#全局作用域" class="headerlink" title="全局作用域"></a>全局作用域</h3><p>一般全局作用域需要定义一个实现 <code>Illuminate\Database\Eloquent\Scope</code> 接口的类,该接口要求你实现一个方法:<code>apply</code>。需要的话可以在 <code>apply</code> 方法中添加 <code>where</code> 条件到查询。</p>
<p>要将全局作用域分配给模型,需要重写给定模型的 <code>boot</code> 方法并使用 <code>addGlobalScope</code> 方法。</p>
<p>另外,我们还可以向 <code>addGlobalScope</code> 中添加匿名函数实现匿名全局作用域。</p>
<p>我们先看看源码:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">addGlobalScope</span><span class="params">($scope, Closure $implementation = null)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (is_string($scope) && ! is_null($implementation)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">static</span>::$globalScopes[<span class="keyword">static</span>::class][$scope] = $implementation;</div><div class="line"> } <span class="keyword">elseif</span> ($scope <span class="keyword">instanceof</span> Closure) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">static</span>::$globalScopes[<span class="keyword">static</span>::class][spl_object_hash($scope)] = $scope;</div><div class="line"> } <span class="keyword">elseif</span> ($scope <span class="keyword">instanceof</span> Scope) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">static</span>::$globalScopes[<span class="keyword">static</span>::class][get_class($scope)] = $scope;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> InvalidArgumentException(<span class="string">'Global scope must be an instance of Closure or Scope.'</span>);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>可以看到,全局作用域使用的是全局的静态变量 <code>globalScopes</code>,该变量保存着所有数据库对象的全局作用域。</p>
<p><code>Eloquent\Model</code> 类并不负责查询功能,相关功能由 <code>Eloquent\Builder</code> 负责,因此每次查询都会间接调用 <code>Eloquent\Builder</code> 类。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__call</span><span class="params">($method, $parameters)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (in_array($method, [<span class="string">'increment'</span>, <span class="string">'decrement'</span>])) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->$method(...$parameters);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->newQuery()->$method(...$parameters);</div><div class="line"> } <span class="keyword">catch</span> (BadMethodCallException $e) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> BadMethodCallException(</div><div class="line"> sprintf(<span class="string">'Call to undefined method %s::%s()'</span>, get_class(<span class="keyword">$this</span>), $method)</div><div class="line"> );</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>创建新的 <code>Eloquent\Builder</code> 类需要 <code>newQuery</code> 函数:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">newQuery</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $builder = <span class="keyword">$this</span>->newQueryWithoutScopes();</div><div class="line"></div><div class="line"> <span class="keyword">foreach</span> (<span class="keyword">$this</span>->getGlobalScopes() <span class="keyword">as</span> $identifier => $scope) {</div><div class="line"> $builder->withGlobalScope($identifier, $scope);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $builder;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getGlobalScopes</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> Arr::get(<span class="keyword">static</span>::$globalScopes, <span class="keyword">static</span>::class, []);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">withGlobalScope</span><span class="params">($identifier, $scope)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">$this</span>->scopes[$identifier] = $scope;</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (method_exists($scope, <span class="string">'extend'</span>)) {</div><div class="line"> $scope->extend(<span class="keyword">$this</span>);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>newQuery</code> 函数为 <code>Eloquent\builder</code> 加载全局作用域,这样静态变量 <code>globalScopes</code> 的值就会被赋到 <code>Eloquent\builder</code> 的 <code>scopes</code> 成员变量中。</p>
<p>当我们使用 <code>get()</code> 函数获取数据库数据的时候,也需要借助魔术方法调用 <code>Illuminate\Database\Eloquent\Builder</code> 类的 <code>get</code> 函数:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">get</span><span class="params">($columns = [<span class="string">'*'</span>])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $builder = <span class="keyword">$this</span>->applyScopes();</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (count($models = $builder->getModels($columns)) > <span class="number">0</span>) {</div><div class="line"> $models = $builder->eagerLoadRelations($models);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $builder->getModel()->newCollection($models);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>调用 <code>applyScopes</code> 函数加载所有的全局作用域:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">applyScopes</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (! <span class="keyword">$this</span>->scopes) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> $builder = <span class="keyword">clone</span> <span class="keyword">$this</span>;</div><div class="line"></div><div class="line"> <span class="keyword">foreach</span> (<span class="keyword">$this</span>->scopes <span class="keyword">as</span> $identifier => $scope) {</div><div class="line"> <span class="keyword">if</span> (! <span class="keyword">isset</span>($builder->scopes[$identifier])) {</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> $builder->callScope(<span class="function"><span class="keyword">function</span> <span class="params">(Builder $builder)</span> <span class="title">use</span> <span class="params">($scope)</span> </span>{</div><div class="line"> <span class="keyword">if</span> ($scope <span class="keyword">instanceof</span> Closure) {</div><div class="line"> $scope($builder);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> ($scope <span class="keyword">instanceof</span> Scope) {</div><div class="line"> $scope->apply($builder, <span class="keyword">$this</span>->getModel());</div><div class="line"> }</div><div class="line"> });</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $builder;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>可以看到,<code>builder</code> 查询类会通过 <code>callScope</code> 加载全局作用域的查询条件。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">callScope</span><span class="params">(callable $scope, $parameters = [])</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> array_unshift($parameters, <span class="keyword">$this</span>);</div><div class="line"></div><div class="line"> $query = <span class="keyword">$this</span>->getQuery();</div><div class="line"></div><div class="line"> $originalWhereCount = is_null($query->wheres)</div><div class="line"> ? <span class="number">0</span> : count($query->wheres);</div><div class="line"></div><div class="line"> $result = $scope(...array_values($parameters)) ?? <span class="keyword">$this</span>;</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (count((<span class="keyword">array</span>) $query->wheres) > $originalWhereCount) {</div><div class="line"> <span class="keyword">$this</span>->addNewWheresWithinGroup($query, $originalWhereCount);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $result;</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>callScope</code> 函数首先会获取更加底层的 <code>Query\builder</code>,更新 <code>query\bulid</code> 的 <code>where</code> 条件。</p>
<p><code>addNewWheresWithinGroup</code> 这个函数很重要,它为 <code>Query\builder</code> 提供 <code>nest</code> 类型的 <code>where</code> 条件:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">addNewWheresWithinGroup</span><span class="params">(QueryBuilder $query, $originalWhereCount)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $allWheres = $query->wheres;</div><div class="line"></div><div class="line"> $query->wheres = [];</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->groupWhereSliceForScope(</div><div class="line"> $query, array_slice($allWheres, <span class="number">0</span>, $originalWhereCount)</div><div class="line"> );</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->groupWhereSliceForScope(</div><div class="line"> $query, array_slice($allWheres, $originalWhereCount)</div><div class="line"> );</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">groupWhereSliceForScope</span><span class="params">(QueryBuilder $query, $whereSlice)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $whereBooleans = collect($whereSlice)->pluck(<span class="string">'boolean'</span>);</div><div class="line"></div><div class="line"> <span class="keyword">if</span> ($whereBooleans->contains(<span class="string">'or'</span>)) {</div><div class="line"> $query->wheres[] = <span class="keyword">$this</span>->createNestedWhere(</div><div class="line"> $whereSlice, $whereBooleans->first()</div><div class="line"> );</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> $query->wheres = array_merge($query->wheres, $whereSlice);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">createNestedWhere</span><span class="params">($whereSlice, $boolean = <span class="string">'and'</span>)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $whereGroup = <span class="keyword">$this</span>->getQuery()->forNestedWhere();</div><div class="line"></div><div class="line"> $whereGroup->wheres = $whereSlice;</div><div class="line"></div><div class="line"> <span class="keyword">return</span> [<span class="string">'type'</span> => <span class="string">'Nested'</span>, <span class="string">'query'</span> => $whereGroup, <span class="string">'boolean'</span> => $boolean];</div><div class="line">}</div></pre></td></tr></table></figure>
<p>当我们在查询作用域中,所有的查询条件连接符都是 <code>and</code> 的时候,可以直接合并到 <code>where</code> 中。</p>
<p>如果我们在查询作用域中或者原查询条件写下了 <code>orWhere</code>、<code>orWhereColumn</code> 等等连接符为 <code>or</code> 的查询条件,那么就会利用 <code>createNestedWhere</code> 函数创建 <code>nest</code> 类型的 <code>where</code> 条件。这个 <code>where</code> 条件会包含查询作用域的所有查询条件,或者原查询的所有查询条件。</p>
<h3 id="本地作用域"><a href="#本地作用域" class="headerlink" title="本地作用域"></a>本地作用域</h3><p>全局作用域会自定加载到所有的查询条件当中,<code>laravel</code> 中还有本地作用域,只有在查询时调用才会生效。</p>
<p>本地作用域是由魔术方法 <code>__call</code> 实现的:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__call</span><span class="params">($method, $parameters)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> ...</div><div class="line"> </div><div class="line"> <span class="keyword">if</span> (method_exists(<span class="keyword">$this</span>->model, $scope = <span class="string">'scope'</span>.ucfirst($method))) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->callScope([<span class="keyword">$this</span>->model, $scope], $parameters);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (in_array($method, <span class="keyword">$this</span>->passthru)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->toBase()->{$method}(...$parameters);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">$this</span>->query->{$method}(...$parameters);</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="批量调用本地作用域"><a href="#批量调用本地作用域" class="headerlink" title="批量调用本地作用域"></a>批量调用本地作用域</h3><p><code>laravel</code> 还提供一个方法可以一次性调用多个本地作用域:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">$scopes = [</div><div class="line"> <span class="string">'published'</span>,</div><div class="line"> <span class="string">'category'</span> => <span class="string">'Laravel'</span>,</div><div class="line"> <span class="string">'framework'</span> => [<span class="string">'Laravel'</span>, <span class="string">'5.3'</span>],</div><div class="line">];</div><div class="line"></div><div class="line">(<span class="keyword">new</span> EloquentModelStub)->scopes($scopes);</div></pre></td></tr></table></figure>
<p>上面的写法会调用三个本地作用域,它们的参数是 <code>$scopes</code> 的值。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">scopes</span><span class="params">(array $scopes)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $builder = <span class="keyword">$this</span>;</div><div class="line"></div><div class="line"> <span class="keyword">foreach</span> ($scopes <span class="keyword">as</span> $scope => $parameters) {</div><div class="line"> <span class="keyword">if</span> (is_int($scope)) {</div><div class="line"> <span class="keyword">list</span>($scope, $parameters) = [$parameters, []];</div><div class="line"> }</div><div class="line"></div><div class="line"> $builder = $builder->callScope(</div><div class="line"> [<span class="keyword">$this</span>->model, <span class="string">'scope'</span>.ucfirst($scope)],</div><div class="line"> (<span class="keyword">array</span>) $parameters</div><div class="line"> );</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $builder;</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="fill-批量赋值"><a href="#fill-批量赋值" class="headerlink" title="fill 批量赋值"></a>fill 批量赋值</h2><p><code>Eloquent Model</code> 默认只能一个一个的设置数据库对象的属性,这是为了保护数据库。但是有的时候,字段过多会造成代码很繁琐。因此,<code>laravel</code> 提供属性批量赋值的功能,<code>fill</code> 函数,相关的官方文档:<a href="https://d.laravel-china.org/docs/5.5/eloquent#mass-assignment" target="_blank" rel="external">批量赋值</a></p>
<h3 id="fill-函数"><a href="#fill-函数" class="headerlink" title="fill 函数"></a>fill 函数</h3><figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">fill</span><span class="params">(array $attributes)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> $totallyGuarded = <span class="keyword">$this</span>->totallyGuarded();</div><div class="line"></div><div class="line"> <span class="keyword">foreach</span> (<span class="keyword">$this</span>->fillableFromArray($attributes) <span class="keyword">as</span> $key => $value) {</div><div class="line"> $key = <span class="keyword">$this</span>->removeTableFromKey($key);</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->isFillable($key)) {</div><div class="line"> <span class="keyword">$this</span>->setAttribute($key, $value);</div><div class="line"> } <span class="keyword">elseif</span> ($totallyGuarded) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> MassAssignmentException($key);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>fill</code> 函数会从参数 <code>attributes</code> 中选取可以批量赋值的属性。所谓的可以批量赋值的属性,是指被 <code>fillable</code> 或 <code>guarded</code> 成员变量设置的参数。被放入 <code>fillable</code> 的属性允许批量赋值的属性,被放入 <code>guarded</code> 的属性禁止批量赋值。</p>
<p>获取可批量赋值的属性:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">fillableFromArray</span><span class="params">(array $attributes)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (count(<span class="keyword">$this</span>->getFillable()) > <span class="number">0</span> && ! <span class="keyword">static</span>::$unguarded) {</div><div class="line"> <span class="keyword">return</span> array_intersect_key($attributes, array_flip(<span class="keyword">$this</span>->getFillable()));</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> $attributes;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getFillable</span><span class="params">()</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->fillable;</div><div class="line">}</div><div class="line">``` </div><div class="line">可以看到,若想要实现批量赋值,需要将属性设置在 `fillable` 成员数组中。</div><div class="line"></div><div class="line">在 `laravel` 中,有一种数据库对象关系是 `morph`,也就是 `多态` 关系,这种关系也会调用 `fill` 函数,这个时候传入的参数 `attributes` 会带有数据库前缀。接下来,就要调用 `removeTableFromKey` 函数来去除数据库前缀:</div><div class="line"></div><div class="line">```php</div><div class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">removeTableFromKey</span><span class="params">($key)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> Str::contains($key, <span class="string">'.'</span>) ? last(explode(<span class="string">'.'</span>, $key)) : $key;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>下一步,还要进一步验证属性的 <code>fillable</code>:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">isFillable</span><span class="params">($key)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">static</span>::$unguarded) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (in_array($key, <span class="keyword">$this</span>->getFillable())) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">$this</span>->isGuarded($key)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">empty</span>(<span class="keyword">$this</span>->getFillable()) &&</div><div class="line"> ! Str::startsWith($key, <span class="string">'_'</span>);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果当前 <code>unguarded</code> 开启,也就是不会保护任何属性,那么直接返回 <code>true</code>。如果当前属性在 <code>fillable</code> 中,也会返回 <code>true</code>。如果当前属性在 <code>guarded</code> 中,返回 <code>false</code>。最后,如果 <code>fillable</code> 是空数组,也会返回 <code>true</code>。</p>
<h3 id="forceFill"><a href="#forceFill" class="headerlink" title="forceFill"></a>forceFill</h3><p>如果不想受 <code>fillable</code> 或者 <code>guarded</code> 等的影响,还可以使用 <code>forceFill</code> 强制来批量赋值。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">forceFill</span><span class="params">(array $attributes)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">static</span>::unguarded(<span class="function"><span class="keyword">function</span> <span class="params">()</span> <span class="title">use</span> <span class="params">($attributes)</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">$this</span>->fill($attributes);</div><div class="line"> });</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">unguarded</span><span class="params">(callable $callback)</span></span></div><div class="line"><span class="function"></span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">static</span>::$unguarded) {</div><div class="line"> <span class="keyword">return</span> $callback();</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">static</span>::unguard();</div><div class="line"></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">return</span> $callback();</div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> <span class="keyword">static</span>::reguard();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<link itemprop="mainEntityOfPage" href="https://leoyang90.github.io/2017/10/02/Laravel Database——查询构造器与语法编译器源码分析(下)/">
<span style="display:none" itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Leo Yang">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/master.jpg">
</span>
<span style="display:none" itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="LEOYANG'S BLOG">
<span style="display:none" itemprop="logo" itemscope itemtype="http://schema.org/ImageObject">
<img style="display:none;" itemprop="url image" alt="LEOYANG'S BLOG" src="">
</span>
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2017/10/02/Laravel Database——查询构造器与语法编译器源码分析(下)/" itemprop="url">
Laravel Database——查询构造器与语法编译器源码分析(下)
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2017-10-02T11:15:00+08:00">
2017-10-02
</time>
</span>
<span class="post-category" >
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/php/" itemprop="url" rel="index">
<span itemprop="name">php</span>
</a>
</span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/php/database/" itemprop="url" rel="index">
<span itemprop="name">database</span>
</a>
</span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/php/database/laravel/" itemprop="url" rel="index">
<span itemprop="name">laravel</span>
</a>
</span>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<a href="/2017/10/02/Laravel Database——查询构造器与语法编译器源码分析(下)/#comments" itemprop="discussionUrl">
<span class="post-comments-count ds-thread-count" data-thread-key="2017/10/02/Laravel Database——查询构造器与语法编译器源码分析(下)/" itemprop="commentCount"></span>
</a>
</span>
<span id="/2017/10/02/Laravel Database——查询构造器与语法编译器源码分析(下)/" class="leancloud_visitors" data-flag-title="Laravel Database——查询构造器与语法编译器源码分析(下)">
<span class="post-meta-divider">|</span>