claude-mpm 5.4.59__py3-none-any.whl → 5.4.64__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +5 -0
- claude_mpm/cli/startup.py +14 -9
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +8 -0
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/pm/pm-delegation-patterns/SKILL.md +167 -0
- claude_mpm/skills/bundled/pm/pm-git-file-tracking/SKILL.md +113 -0
- claude_mpm/skills/bundled/pm/pm-pr-workflow/SKILL.md +124 -0
- claude_mpm/skills/bundled/pm/pm-ticketing-integration/SKILL.md +154 -0
- claude_mpm/skills/bundled/pm/pm-verification-protocols/SKILL.md +198 -0
- claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
- claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
- claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
- claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
- claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
- claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
- claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
- claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
- claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
- claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
- claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- {claude_mpm-5.4.59.dist-info → claude_mpm-5.4.64.dist-info}/METADATA +1 -1
- {claude_mpm-5.4.59.dist-info → claude_mpm-5.4.64.dist-info}/RECORD +128 -11
- {claude_mpm-5.4.59.dist-info → claude_mpm-5.4.64.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.59.dist-info → claude_mpm-5.4.64.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.59.dist-info → claude_mpm-5.4.64.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.59.dist-info → claude_mpm-5.4.64.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.59.dist-info → claude_mpm-5.4.64.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
# EspoCRM Architecture Reference
|
|
2
|
+
|
|
3
|
+
## Metadata-Driven Architecture
|
|
4
|
+
|
|
5
|
+
EspoCRM's core architecture is built on metadata - JSON configuration files that define entities, fields, relationships, and behaviors.
|
|
6
|
+
|
|
7
|
+
### Metadata Structure
|
|
8
|
+
|
|
9
|
+
Metadata lives in several locations with a priority order:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
1. custom/Espo/Custom/Resources/metadata/ (highest priority)
|
|
13
|
+
2. custom/Espo/Modules/{ModuleName}/Resources/metadata/
|
|
14
|
+
3. application/Espo/Modules/{ModuleName}/Resources/metadata/
|
|
15
|
+
4. application/Espo/Resources/metadata/ (lowest priority)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Key Metadata Types
|
|
19
|
+
|
|
20
|
+
**Entity Definitions** (`entityDefs/{EntityType}.json`):
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"fields": {
|
|
24
|
+
"name": {
|
|
25
|
+
"type": "varchar",
|
|
26
|
+
"required": true,
|
|
27
|
+
"maxLength": 255
|
|
28
|
+
},
|
|
29
|
+
"status": {
|
|
30
|
+
"type": "enum",
|
|
31
|
+
"options": ["New", "In Progress", "Complete"],
|
|
32
|
+
"default": "New"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"links": {
|
|
36
|
+
"account": {
|
|
37
|
+
"type": "belongsTo",
|
|
38
|
+
"entity": "Account",
|
|
39
|
+
"foreign": "contacts"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Client Definitions** (`clientDefs/{EntityType}.json`):
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"controller": "custom:controllers/my-entity",
|
|
49
|
+
"views": {
|
|
50
|
+
"detail": "custom:views/my-entity/detail"
|
|
51
|
+
},
|
|
52
|
+
"recordViews": {
|
|
53
|
+
"detail": "custom:views/my-entity/record/detail"
|
|
54
|
+
},
|
|
55
|
+
"sidePanels": {
|
|
56
|
+
"detail": [
|
|
57
|
+
{
|
|
58
|
+
"name": "activities",
|
|
59
|
+
"label": "Activities",
|
|
60
|
+
"view": "crm:views/record/panels/activities"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Scopes** (`scopes/{EntityType}.json`):
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"entity": true,
|
|
71
|
+
"object": true,
|
|
72
|
+
"layouts": true,
|
|
73
|
+
"tab": true,
|
|
74
|
+
"acl": true,
|
|
75
|
+
"module": "MyModule",
|
|
76
|
+
"stream": true
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Metadata Access in Code
|
|
81
|
+
|
|
82
|
+
```php
|
|
83
|
+
use Espo\Core\Utils\Metadata;
|
|
84
|
+
|
|
85
|
+
class MyService {
|
|
86
|
+
public function __construct(private Metadata $metadata) {}
|
|
87
|
+
|
|
88
|
+
public function getEntityFields(string $entityType): array {
|
|
89
|
+
return $this->metadata->get(['entityDefs', $entityType, 'fields']) ?? [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public function isFieldRequired(string $entityType, string $field): bool {
|
|
93
|
+
return $this->metadata
|
|
94
|
+
->get(['entityDefs', $entityType, 'fields', $field, 'required']) ?? false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## ORM EntityManager
|
|
100
|
+
|
|
101
|
+
EntityManager is the central access point for ALL database operations in EspoCRM.
|
|
102
|
+
|
|
103
|
+
### Core EntityManager Methods
|
|
104
|
+
|
|
105
|
+
```php
|
|
106
|
+
use Espo\ORM\EntityManager;
|
|
107
|
+
|
|
108
|
+
class DataService {
|
|
109
|
+
public function __construct(private EntityManager $entityManager) {}
|
|
110
|
+
|
|
111
|
+
// Get entity by ID
|
|
112
|
+
public function getById(string $entityType, string $id): ?Entity {
|
|
113
|
+
return $this->entityManager->getEntityById($entityType, $id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Create new entity
|
|
117
|
+
public function create(string $entityType): Entity {
|
|
118
|
+
return $this->entityManager->getNewEntity($entityType);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Save entity
|
|
122
|
+
public function save(Entity $entity): void {
|
|
123
|
+
$this->entityManager->saveEntity($entity);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Delete entity
|
|
127
|
+
public function delete(Entity $entity): void {
|
|
128
|
+
$this->entityManager->removeEntity($entity);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Get repository
|
|
132
|
+
public function getRepository(string $entityType): RDBRepository {
|
|
133
|
+
return $this->entityManager->getRDBRepository($entityType);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Repository Pattern
|
|
139
|
+
|
|
140
|
+
Never access repositories directly - always through EntityManager:
|
|
141
|
+
|
|
142
|
+
```php
|
|
143
|
+
// Query with conditions
|
|
144
|
+
$contacts = $this->entityManager
|
|
145
|
+
->getRDBRepository('Contact')
|
|
146
|
+
->where([
|
|
147
|
+
'accountId' => $accountId,
|
|
148
|
+
'deleted' => false
|
|
149
|
+
])
|
|
150
|
+
->find();
|
|
151
|
+
|
|
152
|
+
// Complex queries
|
|
153
|
+
$query = $this->entityManager
|
|
154
|
+
->getQueryBuilder()
|
|
155
|
+
->select()
|
|
156
|
+
->from('Opportunity')
|
|
157
|
+
->where([
|
|
158
|
+
'stage' => ['Proposal', 'Negotiation'],
|
|
159
|
+
'amount>=' => 10000
|
|
160
|
+
])
|
|
161
|
+
->order('createdAt', 'DESC')
|
|
162
|
+
->build();
|
|
163
|
+
|
|
164
|
+
$collection = $this->entityManager
|
|
165
|
+
->getRDBRepository('Opportunity')
|
|
166
|
+
->clone($query)
|
|
167
|
+
->find();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Transaction Handling
|
|
171
|
+
|
|
172
|
+
```php
|
|
173
|
+
use Espo\ORM\TransactionManager;
|
|
174
|
+
|
|
175
|
+
class TransactionalService {
|
|
176
|
+
public function __construct(
|
|
177
|
+
private EntityManager $entityManager,
|
|
178
|
+
private TransactionManager $transactionManager
|
|
179
|
+
) {}
|
|
180
|
+
|
|
181
|
+
public function performComplexOperation(): void {
|
|
182
|
+
$this->transactionManager->run(function () {
|
|
183
|
+
// All operations within this closure are transactional
|
|
184
|
+
$entity1 = $this->entityManager->getNewEntity('Account');
|
|
185
|
+
$entity1->set('name', 'Test');
|
|
186
|
+
$this->entityManager->saveEntity($entity1);
|
|
187
|
+
|
|
188
|
+
$entity2 = $this->entityManager->getNewEntity('Contact');
|
|
189
|
+
$entity2->set('accountId', $entity1->getId());
|
|
190
|
+
$this->entityManager->saveEntity($entity2);
|
|
191
|
+
|
|
192
|
+
// If any exception is thrown, all changes are rolled back
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### STH Collections for Large Datasets
|
|
199
|
+
|
|
200
|
+
For operations on large datasets, use STH (Statement Handle) collections to avoid memory issues:
|
|
201
|
+
|
|
202
|
+
```php
|
|
203
|
+
$sthCollection = $this->entityManager
|
|
204
|
+
->getRDBRepository('Contact')
|
|
205
|
+
->sth() // Returns STH collection instead of loading all into memory
|
|
206
|
+
->where(['accountId' => $accountId])
|
|
207
|
+
->find();
|
|
208
|
+
|
|
209
|
+
foreach ($sthCollection as $contact) {
|
|
210
|
+
// Process one at a time
|
|
211
|
+
$contact->set('status', 'Active');
|
|
212
|
+
$this->entityManager->saveEntity($contact);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Dependency Injection Container
|
|
217
|
+
|
|
218
|
+
EspoCRM uses a DI container for dependency management.
|
|
219
|
+
|
|
220
|
+
### Constructor Injection (CORRECT)
|
|
221
|
+
|
|
222
|
+
```php
|
|
223
|
+
namespace Espo\Modules\MyModule\Services;
|
|
224
|
+
|
|
225
|
+
use Espo\ORM\EntityManager;
|
|
226
|
+
use Espo\Core\Utils\Metadata;
|
|
227
|
+
use Espo\Core\Mail\EmailSender;
|
|
228
|
+
|
|
229
|
+
class MyService {
|
|
230
|
+
public function __construct(
|
|
231
|
+
private EntityManager $entityManager,
|
|
232
|
+
private Metadata $metadata,
|
|
233
|
+
private EmailSender $emailSender
|
|
234
|
+
) {}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### NEVER Pass Container
|
|
239
|
+
|
|
240
|
+
```php
|
|
241
|
+
// ❌ WRONG - Never do this
|
|
242
|
+
use Espo\Core\Container;
|
|
243
|
+
|
|
244
|
+
class BadService {
|
|
245
|
+
public function __construct(private Container $container) {}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ✅ CORRECT - Inject specific dependencies
|
|
249
|
+
class GoodService {
|
|
250
|
+
public function __construct(
|
|
251
|
+
private EntityManager $entityManager,
|
|
252
|
+
private Metadata $metadata
|
|
253
|
+
) {}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Injectable Services
|
|
258
|
+
|
|
259
|
+
Common services available for injection:
|
|
260
|
+
|
|
261
|
+
- `EntityManager` - ORM access
|
|
262
|
+
- `Metadata` - Metadata access
|
|
263
|
+
- `Config` - Application configuration
|
|
264
|
+
- `FileStorageManager` - File operations
|
|
265
|
+
- `InjectableFactory` - Create objects with DI
|
|
266
|
+
- `ServiceFactory` - Access record services
|
|
267
|
+
- `EmailSender` - Send emails
|
|
268
|
+
- `Acl` - Access control
|
|
269
|
+
- `User` - Current user
|
|
270
|
+
- `DateTime` - Date/time utilities
|
|
271
|
+
- `Language` - Translations
|
|
272
|
+
- `TransactionManager` - Database transactions
|
|
273
|
+
|
|
274
|
+
## Service Layer Architecture
|
|
275
|
+
|
|
276
|
+
Business logic belongs in Service classes, not hooks or controllers.
|
|
277
|
+
|
|
278
|
+
### Service Hierarchy
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
Record Service (base for all entity services)
|
|
282
|
+
↓
|
|
283
|
+
Custom Service (your entity-specific logic)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Extending Record Service
|
|
287
|
+
|
|
288
|
+
```php
|
|
289
|
+
namespace Espo\Modules\MyModule\Services;
|
|
290
|
+
|
|
291
|
+
use Espo\Services\Record;
|
|
292
|
+
use Espo\ORM\Entity;
|
|
293
|
+
|
|
294
|
+
class Opportunity extends Record {
|
|
295
|
+
// Override to add custom logic before create
|
|
296
|
+
protected function beforeCreateEntity(Entity $entity, array $data): void {
|
|
297
|
+
parent::beforeCreateEntity($entity, $data);
|
|
298
|
+
|
|
299
|
+
// Custom logic
|
|
300
|
+
if ($entity->get('amount') > 100000) {
|
|
301
|
+
$entity->set('priority', 'High');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Custom action
|
|
306
|
+
public function markAsWon(string $id): Entity {
|
|
307
|
+
$entity = $this->getEntity($id);
|
|
308
|
+
|
|
309
|
+
if (!$entity) {
|
|
310
|
+
throw new NotFound();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
$entity->set('stage', 'Closed Won');
|
|
314
|
+
$this->entityManager->saveEntity($entity);
|
|
315
|
+
|
|
316
|
+
// Trigger additional business logic
|
|
317
|
+
$this->createWinNotification($entity);
|
|
318
|
+
|
|
319
|
+
return $entity;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private function createWinNotification(Entity $opportunity): void {
|
|
323
|
+
// Implementation
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Service Access
|
|
329
|
+
|
|
330
|
+
```php
|
|
331
|
+
use Espo\Core\ServiceFactory;
|
|
332
|
+
|
|
333
|
+
class MyClass {
|
|
334
|
+
public function __construct(private ServiceFactory $serviceFactory) {}
|
|
335
|
+
|
|
336
|
+
public function useService(): void {
|
|
337
|
+
$opportunityService = $this->serviceFactory->create('Opportunity');
|
|
338
|
+
$opportunityService->markAsWon($id);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Hook System Architecture
|
|
344
|
+
|
|
345
|
+
Hooks are for lifecycle events - validation and side effects ONLY. Business logic belongs in Services.
|
|
346
|
+
|
|
347
|
+
### The 7 Hook Interfaces
|
|
348
|
+
|
|
349
|
+
```php
|
|
350
|
+
namespace Espo\Core\Hook\Hook;
|
|
351
|
+
|
|
352
|
+
interface BeforeSave {
|
|
353
|
+
public function beforeSave(Entity $entity, array $options): void;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
interface AfterSave {
|
|
357
|
+
public function afterSave(Entity $entity, array $options): void;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
interface BeforeRemove {
|
|
361
|
+
public function beforeRemove(Entity $entity, array $options): void;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
interface AfterRemove {
|
|
365
|
+
public function afterRemove(Entity $entity, array $options): void;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
interface AfterRelate {
|
|
369
|
+
public function afterRelate(Entity $entity, string $relationName, Entity $foreign, ?array $columnData, array $options): void;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
interface AfterUnrelate {
|
|
373
|
+
public function afterUnrelate(Entity $entity, string $relationName, Entity $foreign, array $options): void;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
interface AfterMassRelate {
|
|
377
|
+
public function afterMassRelate(Entity $entity, string $relationName, array $params, array $options): void;
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Hook Implementation Example
|
|
382
|
+
|
|
383
|
+
```php
|
|
384
|
+
namespace Espo\Modules\MyModule\Hooks\Account;
|
|
385
|
+
|
|
386
|
+
use Espo\ORM\Entity;
|
|
387
|
+
use Espo\Core\Hook\Hook\BeforeSave;
|
|
388
|
+
use Espo\Core\ServiceFactory;
|
|
389
|
+
|
|
390
|
+
class ValidateWebsite implements BeforeSave {
|
|
391
|
+
public function __construct(private ServiceFactory $serviceFactory) {}
|
|
392
|
+
|
|
393
|
+
public function beforeSave(Entity $entity, array $options): void {
|
|
394
|
+
// Validation only
|
|
395
|
+
if ($entity->isAttributeChanged('website')) {
|
|
396
|
+
$website = $entity->get('website');
|
|
397
|
+
if ($website && !filter_var($website, FILTER_VALIDATE_URL)) {
|
|
398
|
+
throw new \Espo\Core\Exceptions\BadRequest('Invalid website URL');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Hook Registration
|
|
406
|
+
|
|
407
|
+
Hooks are auto-discovered in:
|
|
408
|
+
```
|
|
409
|
+
custom/Espo/Modules/{ModuleName}/Hooks/{EntityType}/{HookName}.php
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Coding Standards
|
|
413
|
+
|
|
414
|
+
### Type Declarations (Required)
|
|
415
|
+
|
|
416
|
+
```php
|
|
417
|
+
// ✅ CORRECT - All types declared
|
|
418
|
+
class MyService {
|
|
419
|
+
public function processData(string $id, array $data): object {
|
|
420
|
+
return $this->entityManager->getEntityById('Account', $id);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ❌ WRONG - Missing types
|
|
425
|
+
class BadService {
|
|
426
|
+
public function processData($id, $data) {
|
|
427
|
+
return $this->entityManager->getEntityById('Account', $id);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Exception Handling (Not Booleans)
|
|
433
|
+
|
|
434
|
+
```php
|
|
435
|
+
// ✅ CORRECT - Use exceptions
|
|
436
|
+
use Espo\Core\Exceptions\{NotFound, Forbidden, BadRequest};
|
|
437
|
+
|
|
438
|
+
public function getAccount(string $id): Entity {
|
|
439
|
+
$account = $this->entityManager->getEntityById('Account', $id);
|
|
440
|
+
|
|
441
|
+
if (!$account) {
|
|
442
|
+
throw new NotFound();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (!$this->acl->check($account, 'read')) {
|
|
446
|
+
throw new Forbidden();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return $account;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ❌ WRONG - Returning booleans for errors
|
|
453
|
+
public function getAccount(string $id): ?Entity {
|
|
454
|
+
$account = $this->entityManager->getEntityById('Account', $id);
|
|
455
|
+
if (!$account) {
|
|
456
|
+
return null; // Lost error context
|
|
457
|
+
}
|
|
458
|
+
return $account;
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Composition Over Inheritance
|
|
463
|
+
|
|
464
|
+
```php
|
|
465
|
+
// ✅ CORRECT - Composition with traits/utilities
|
|
466
|
+
class MyService extends Record {
|
|
467
|
+
use ValidationTrait;
|
|
468
|
+
|
|
469
|
+
public function __construct(
|
|
470
|
+
private ValidationHelper $validationHelper,
|
|
471
|
+
private NotificationHelper $notificationHelper
|
|
472
|
+
) {
|
|
473
|
+
parent::__construct();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ❌ WRONG - Deep inheritance hierarchy
|
|
478
|
+
class MyService extends IntermediateService extends BaseService extends Record {
|
|
479
|
+
// Too many levels
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### DTOs Over Arrays
|
|
484
|
+
|
|
485
|
+
```php
|
|
486
|
+
// ✅ CORRECT - Use DTOs
|
|
487
|
+
class CreateAccountData {
|
|
488
|
+
public function __construct(
|
|
489
|
+
public readonly string $name,
|
|
490
|
+
public readonly ?string $website,
|
|
491
|
+
public readonly array $tags
|
|
492
|
+
) {}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
public function createAccount(CreateAccountData $data): Entity {
|
|
496
|
+
// Type-safe operations
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ❌ WRONG - Untyped arrays
|
|
500
|
+
public function createAccount(array $data): Entity {
|
|
501
|
+
$name = $data['name'] ?? ''; // Fragile, no IDE support
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Maximum 2 Indentation Levels
|
|
506
|
+
|
|
507
|
+
```php
|
|
508
|
+
// ✅ CORRECT - Early returns, extracted methods
|
|
509
|
+
public function process(Entity $entity): void {
|
|
510
|
+
if (!$this->validate($entity)) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
$this->performUpdate($entity);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private function performUpdate(Entity $entity): void {
|
|
518
|
+
if ($entity->isNew()) {
|
|
519
|
+
$this->handleNew($entity);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
$this->handleExisting($entity);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ❌ WRONG - Deep nesting
|
|
527
|
+
public function process(Entity $entity): void {
|
|
528
|
+
if ($this->validate($entity)) {
|
|
529
|
+
if ($entity->isNew()) {
|
|
530
|
+
if ($this->hasPermission()) {
|
|
531
|
+
// Three levels deep - hard to read
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Formula Scripting
|
|
539
|
+
|
|
540
|
+
EspoCRM supports declarative logic through Formula scripts - use for simple field calculations instead of hooks.
|
|
541
|
+
|
|
542
|
+
### Formula in Metadata
|
|
543
|
+
|
|
544
|
+
```json
|
|
545
|
+
{
|
|
546
|
+
"fields": {
|
|
547
|
+
"totalPrice": {
|
|
548
|
+
"type": "currency",
|
|
549
|
+
"formula": "quantity * unitPrice"
|
|
550
|
+
},
|
|
551
|
+
"displayName": {
|
|
552
|
+
"type": "varchar",
|
|
553
|
+
"formula": "string\\concatenate(firstName, ' ', lastName)"
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### When to Use Formula vs. Hooks
|
|
560
|
+
|
|
561
|
+
**Use Formula for:**
|
|
562
|
+
- Simple field calculations
|
|
563
|
+
- String concatenation
|
|
564
|
+
- Conditional field values
|
|
565
|
+
- Date calculations
|
|
566
|
+
|
|
567
|
+
**Use Hooks/Services for:**
|
|
568
|
+
- Complex business logic
|
|
569
|
+
- External API calls
|
|
570
|
+
- Multi-entity operations
|
|
571
|
+
- Validation requiring database queries
|
|
572
|
+
|
|
573
|
+
## Cache Management
|
|
574
|
+
|
|
575
|
+
### Rebuild Cache After Metadata Changes
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
# Always run after changing metadata
|
|
579
|
+
bin/command rebuild
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Clear Cache Programmatically
|
|
583
|
+
|
|
584
|
+
```php
|
|
585
|
+
use Espo\Core\Utils\DataCache;
|
|
586
|
+
|
|
587
|
+
class MyService {
|
|
588
|
+
public function __construct(private DataCache $dataCache) {}
|
|
589
|
+
|
|
590
|
+
public function clearCache(): void {
|
|
591
|
+
$this->dataCache->clear();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Cache Keys
|
|
597
|
+
|
|
598
|
+
Common cache keys to be aware of:
|
|
599
|
+
- `metadata` - All metadata
|
|
600
|
+
- `entityDefs` - Entity definitions
|
|
601
|
+
- `clientDefs` - Client definitions
|
|
602
|
+
- `aclDefs` - ACL definitions
|