This commit is contained in:
2026-06-23 11:12:28 +02:00
parent f2ea5e160b
commit 557bf3fcdf
2458 changed files with 323084 additions and 316 deletions
+1
View File
@@ -0,0 +1 @@
{"version":2,"defects":{"Tests\\Feature\\DocumentoLinkTest::test_admin_can_create_link_document":7,"Tests\\Feature\\DocumentoLinkTest::test_create_link_requires_url":8,"Tests\\Feature\\DocumentoLinkTest::test_create_link_validates_url_format":8,"Tests\\Feature\\DocumentoLinkTest::test_create_link_does_not_require_file":7,"Tests\\Feature\\DocumentoLinkTest::test_create_link_sets_tipo_to_link":7,"Tests\\Feature\\DocumentoLinkTest::test_create_link_with_visibility_individuo":7,"Tests\\Feature\\DocumentoLinkTest::test_create_link_with_visibility_gruppo":7,"Tests\\Feature\\DocumentoLinkTest::test_download_link_redirects_to_url":8,"Tests\\Feature\\DocumentoLinkTest::test_preview_link_returns_fallback_view":8,"Tests\\Feature\\DocumentoLinkTest::test_update_link_url":8,"Tests\\Feature\\DocumentoLinkTest::test_update_link_preserves_url_when_not_sent":8,"Tests\\Feature\\DocumentoLinkTest::test_update_link_validates_url_format":8,"Tests\\Feature\\DocumentoLinkTest::test_destroy_link_does_not_call_storage":8,"Tests\\Feature\\DocumentoLinkTest::test_mass_download_skips_links":7},"times":{"Tests\\Unit\\DocumentoLinkTest::test_is_link_returns_true_when_tipo_is_link":0.001,"Tests\\Unit\\DocumentoLinkTest::test_is_link_returns_false_when_tipo_is_upload":0.001,"Tests\\Unit\\DocumentoLinkTest::test_is_link_returns_false_when_tipo_is_allegato":0.001,"Tests\\Unit\\DocumentoLinkTest::test_is_link_returns_false_when_tipo_is_null":0.002,"Tests\\Unit\\DocumentoLinkTest::test_url_is_fillable":0.001,"Tests\\Unit\\DocumentoLinkTest::test_link_doc_has_no_file_fields":0.001,"Tests\\Feature\\DocumentoLinkTest::test_admin_can_create_link_document":0.064,"Tests\\Feature\\DocumentoLinkTest::test_create_link_requires_url":0.008,"Tests\\Feature\\DocumentoLinkTest::test_create_link_validates_url_format":0.007,"Tests\\Feature\\DocumentoLinkTest::test_create_link_does_not_require_file":0.006,"Tests\\Feature\\DocumentoLinkTest::test_create_link_sets_tipo_to_link":0.014,"Tests\\Feature\\DocumentoLinkTest::test_create_link_with_visibility_individuo":0.008,"Tests\\Feature\\DocumentoLinkTest::test_create_link_with_visibility_gruppo":0.008,"Tests\\Feature\\DocumentoLinkTest::test_download_link_redirects_to_url":0.005,"Tests\\Feature\\DocumentoLinkTest::test_preview_link_returns_fallback_view":0.009,"Tests\\Feature\\DocumentoLinkTest::test_update_link_url":0.008,"Tests\\Feature\\DocumentoLinkTest::test_update_link_preserves_url_when_not_sent":0.006,"Tests\\Feature\\DocumentoLinkTest::test_update_link_validates_url_format":0.006,"Tests\\Feature\\DocumentoLinkTest::test_destroy_link_does_not_call_storage":0.004,"Tests\\Feature\\DocumentoLinkTest::test_mass_download_skips_links":0.004}}
+58 -2
View File
@@ -42,6 +42,19 @@ App gestionale Laravel 13 con AdminLTE 4 per gestione Persone e Gruppi.
- `run()` restituisce `steps` array; mostrato in flash message (controller) e output (command)
- **2026-06-18**: Diagnose script (`diagnose.php`), .env.example aggiornato (SESSION_SECURE_COOKIE, FORCE_HTTPS, TRUSTED_PROXIES), build-dist.sh aggiornato con passo diagnose
- **2026-06-23**: Usabilità viste colonne: se non si clicca ✏️ su una vista, la vista attiva corrente viene auto-assunta come target di modifica. Se il nome viene cambiato, crea nuova vista invece di aggiornare (rilevamento rename via `currentVistaOriginalNome`)
- **2026-06-23**: Aggiunto campo "Anno fondazione" (anno_fondazione) a Gruppo — migration, model (+fillable/+casts), controller (validazione store/update, import CSV, template CSV), create/edit/show/index views, import view field list
- **2026-06-23**: Implementato "Documento come URL esterno" (link a risorsa esterna) — piena integrazione in email, mailing, preview, download, UI
- Migration: `url` text nullable after `note`
- Model: `url` in `$fillable` + metodo `isLink(): bool` (controlla `tipo === 'link'`)
- DocumentoController: store() validazione condizionale file/URL via radio `tipo_caricamento`; update() campo URL condizionale; download() redirect away per link; preview() mostra fallback con pulsante "Apri link esterno"; massDownload() skip link-docs con counter warning; destroy() skip rimozione file
- EmailController: processAttachments/resolveAttachmentPaths skip URL-docs dall'allegato fisico, accumulano links array; `appendLinksToBody()` inserisce link come testo nel corpo email prima della firma
- MailingController: resolveMailingAttachmentPaths skip URL-docs, accumula links; `appendMailingLinksToBody()` stesso pattern
- Upload modal (index.blade.php): toggle radio File/URL con campi condizionali (JS `toggleTipoCaricamento()`)
- Grid/list view: icona link (`fa-link`) invece di mime icon per URL-docs; pulsante "Apri link esterno" invece di preview/download; move button hidden per link
- Edit view (edit.blade.php): se link, mostra campo URL editabile + info URL nel sidebar invece di file_path/mime/dimensione
- Preview fallback (preview-fallback.blade.php): supporto URL-docs con icona link + pulsante "Apri link esterno"
- Email/mailing document selection table: badge "URL" nella riga documento
- Route `documenti/{id}/download` per URL-docs: redirect HTTP 302 all'URL esterno
## Funzionalità Implementate
@@ -331,8 +344,7 @@ if (!empty($contatto['individuo_id'])) { create }
```
## Prossimi Passi
- [DONE] ... (existing items)
- Verificare end-to-end su remote: tutte le nuove mass action, mailing list con contatti senza email, ricerca multi-tag
- [DONE] Verificare end-to-end su remote: tutte le nuove mass action, mailing list con contatti senza email, ricerca multi-tag
- [DONE] Fix performance: spostata query `VistaReport::where(...)->get()` da `@php` nel partial `table-settings.blade.php` a tutti i 5 controller (prima veniva eseguita 1 query per ogni pagina load per ogni entity, anche senza mai aprire la modale)
- [DONE] Rimosse vecchie modal legacy (`#saveVistaModal`, `#colonneModal`, `#vistaListModal`) da `individui` e `gruppi` — duplicate rispetto al nuovo modal unificato
- [DONE] Rimosse vecchie funzioni JS (`saveVista()`, `toggleColumn()`, `showSaveVistaModal()`) da `individui` e `gruppi`
@@ -1115,3 +1127,47 @@ Fasi install (9 step): come upgrade + setup .env con pausa interattiva
- JS brace balance: OK (diff=0)
- Tutti gli altri listener modal nel file usano già jQuery `.on()` — il fix allinea `massMoveModal` al pattern esistente
- Nessun test automatizzato presente nel progetto (no Pest, no PHPUnit config)
## 2026-06-23 — Test + Fix migration per compatibilità SQLite + massDownload unlink fix
### Tests scritti
- **`tests/Unit/DocumentoLinkTest.php`**: 6 unit test per `Documento::isLink()` — copre tutti i valori `tipo` (link, upload, allegato, null), verifica `url` fillable, verifica link-doc senza file fields
- **`tests/Feature/DocumentoLinkTest.php`**: 14 feature test per link-doc CRUD:
- `store` con link: creazione base, validazione required url, validazione formato url, skip file, set tipo=link, visibilità individuo, visibilità gruppo
- `update`: modifica URL, preserva URL se non inviato, validazione formato
- `download`: redirect away all'URL esterno
- `preview`: vista fallback con header X-Preview-Fallback
- `destroy`: delete senza chiamate Storage
- `massDownload`: skip link-doc con errore "Nessun file disponibile"
- **Totale**: 20 test, 45 assertion, tutti ✅ passanti su SQLite `:memory:`
### Fix migration per compatibilità SQLite (test cross-DB)
3 migration non compatibili con SQLite fixate per permettere test su `:memory:`:
1. **`2026_05_12_000002_change_ruolo_to_multi.php`**:
- Aggiunto `Schema::hasColumn('gruppo_individuo', 'ruolo_id')` guard prima di `dropForeign`/`dropColumn`
- Aggiunto `dropIndex(['ruolo_id'])` prima di `dropColumn` (SQLite richiede drop index esplicito)
2. **`2024_01_01_000008_create_documenti_table.php`**:
- `$table->enum('tipologia', [...])``$table->string('tipologia', 50)` (app-level validation)
- `$table->enum('visibilita', [...])``$table->string('visibilita', 50)` (app-level validation)
- `$table->string('file_path')``$table->string('file_path')->nullable()` (necessario per link-docs)
3. **`2026_05_11_000005_add_email_attachment_to_documenti.php`**: reso no-op (colonna già string, app-level validation)
4. **`2026_05_13_072203_add_programma_locandina_to_tipologia_enum.php`**: reso no-op (colonna già string, app-level validation)
### Fix massDownload unlink edge case
- **`app/Http/Controllers/DocumentoController.php:710`**: Aggiunto `file_exists($zipPath)` guard prima di `@unlink($zipPath)` — preveniva `ErrorException` quando ZipArchive non creava file su alcuni filesystem (documenti tutti link → addedCount=0 → file zip non creato fisicamente → unlink falliva)
### Fix produzione: file_path NOT NULL su MySQL
- **Problema**: La colonna `file_path` su MySQL è `NOT NULL` (migrazione originale `$table->string('file_path')`). Inserendo un link-doc (`tipo='link'`) senza `file_path`, MySQL rifiuta: `Field 'file_path' doesn't have a default value`.
- **Fix**: Nuova migration `2026_06_23_085637_make_file_path_nullable_in_documenti.php``ALTER TABLE documenti MODIFY COLUMN file_path VARCHAR(255) NULL` su MySQL
- **Eseguita** localmente ✅ (80ms)
### build-dist.sh
- Nessuna modifica necessaria: `php artisan migrate --force` è già presente in `post-deploy.sh` sia per upgrade (`[5/10]`) che per install (`[8/11]`)
### Prossimi Passi
- ✅ Eseguire `php artisan migrate` su produzione per rendere `file_path` nullable
- Verificare in ambiente di staging: creazione link-doc (tutte le visibilità), visualizzazione grid/list, preview fallback, download (redirect), modifica URL, eliminazione, mass-download con skip link, email/mailing con link nel corpo
- Test edge case: URL malformato (validazione update), link senza url, link con cartella_id, link con visibilità individuo/gruppo
+107 -24
View File
@@ -201,7 +201,7 @@ class DocumentoController extends Controller
$tipologieValidi = TipologiaDocumento::opzioni();
$tipologieRule = 'in:' . implode(',', $tipologieValidi);
$data = $request->validate([
$rules = [
'nome_file' => 'required|string|max:255',
'tipologia' => 'required|' . $tipologieRule,
'contesto_tipo' => 'nullable|in:individuo,gruppo,evento,mailing',
@@ -209,7 +209,13 @@ class DocumentoController extends Controller
'note' => 'nullable|string',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
]);
];
if ($documento->isLink()) {
$rules['url'] = 'nullable|url|max:2048';
}
$data = $request->validate($rules);
$contestoTipo = $data['contesto_tipo'] ?? null;
$data['visibilita'] = $contestoTipo ?: 'pubblico';
@@ -228,6 +234,10 @@ class DocumentoController extends Controller
$data['visibilita_target_type'] = null;
}
if ($documento->isLink()) {
$data['url'] = $data['url'] ?? $documento->url;
}
$documento->update($data);
if ($request->has('tags')) {
@@ -246,10 +256,11 @@ class DocumentoController extends Controller
$tipologieValidi = TipologiaDocumento::opzioni();
$tipologieRule = 'in:' . implode(',', $tipologieValidi);
$data = $request->validate([
$isLink = $request->input('tipo_caricamento') === 'link';
$rules = [
'nome_file' => 'required|string|max:255',
'tipologia' => 'required|' . $tipologieRule,
'file' => 'required|file|max:10240',
'visibilita' => 'required|in:pubblico,individuo,gruppo',
'visibilita_target_id' => 'nullable|integer',
'visibilita_target_type' => 'nullable|string',
@@ -257,7 +268,47 @@ class DocumentoController extends Controller
'repository_id' => 'nullable|integer|exists:storage_repositories,id',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
]);
'tipo_caricamento' => 'required|in:file,link',
'url' => 'required_if:tipo_caricamento,link|nullable|url|max:2048',
];
if (!$isLink) {
$rules['file'] = 'required|file|max:10240';
}
$data = $request->validate($rules);
if ($isLink) {
$linkVisibilitaTargetType = null;
if ($data['visibilita'] === 'individuo' && $data['visibilita_target_id']) {
$linkVisibilitaTargetType = Individuo::class;
} elseif ($data['visibilita'] === 'gruppo' && $data['visibilita_target_id']) {
$linkVisibilitaTargetType = Gruppo::class;
}
$doc = Documento::create([
'nome_file' => $data['nome_file'],
'url' => $data['url'],
'tipo' => 'link',
'tipologia' => $data['tipologia'],
'visibilita' => $data['visibilita'],
'visibilita_target_id' => $data['visibilita_target_id'] ?? null,
'visibilita_target_type' => $linkVisibilitaTargetType,
'user_id' => auth()->id(),
'cartella_id' => $data['cartella_id'] ?? null,
'repository_id' => null,
]);
if ($doc && $request->has('tags')) {
$doc->tags()->sync($request->tags);
}
$redirect = $request->_redirect ?? url()->previous();
if ($request->filled('folder_id')) {
$redirect = '/documenti?folder_id=' . $request->input('folder_id');
}
return redirect($redirect)->with('success', 'Documento (link) creato.');
}
$repositoryId = $data['repository_id'] ?? null;
@@ -321,6 +372,10 @@ class DocumentoController extends Controller
$this->authorizeRead('documenti');
$documento = Documento::findOrFail($documento);
if ($documento->isLink()) {
return redirect()->away($documento->url);
}
$extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
$filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;
@@ -366,6 +421,12 @@ class DocumentoController extends Controller
$this->authorizeRead('documenti');
$documento = Documento::findOrFail($documento);
if ($documento->isLink()) {
return response()->view('documenti.preview-fallback', [
'documento' => $documento,
])->header('X-Preview-Fallback', 'true');
}
$previewableMimeTypes = [
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
'application/pdf',
@@ -416,7 +477,8 @@ class DocumentoController extends Controller
$this->authorizeDelete('documenti');
$documento = Documento::findOrFail($documento);
if ($documento->repository_id) {
if (!$documento->isLink()) {
if ($documento->repository_id) {
$repo = StorageRepository::find($documento->repository_id);
if ($repo) {
$filesystem = $this->repoService->buildFilesystem($repo);
@@ -424,10 +486,11 @@ class DocumentoController extends Controller
$filesystem->delete($documento->file_path);
}
}
} else {
$disk = $this->resolveStorageDisk($documento);
if (Storage::disk($disk)->exists($documento->file_path)) {
Storage::disk($disk)->delete($documento->file_path);
} else {
$disk = $this->resolveStorageDisk($documento);
if (Storage::disk($disk)->exists($documento->file_path)) {
Storage::disk($disk)->delete($documento->file_path);
}
}
}
@@ -604,8 +667,14 @@ class DocumentoController extends Controller
return back()->with('error', 'Impossibile creare l\'archivio ZIP.');
}
$skippedLinks = 0;
$addedCount = 0;
foreach ($documenti as $documento) {
if ($documento->isLink()) {
$skippedLinks++;
continue;
}
$extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
$filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;
@@ -638,11 +707,23 @@ class DocumentoController extends Controller
$zip->close();
if ($addedCount === 0) {
unlink($zipPath);
return back()->with('error', 'Nessun file disponibile per il download.');
if (file_exists($zipPath)) {
@unlink($zipPath);
}
$message = 'Nessun file disponibile per il download.';
if ($skippedLinks > 0) {
$message .= " ({$skippedLinks} link esterni saltati)";
}
return back()->with('error', $message);
}
return response()->download($zipPath, 'documenti.zip')->deleteFileAfterSend(true);
$message = $addedCount . ' file scaricati.';
if ($skippedLinks > 0) {
$message .= " {$skippedLinks} link esterni saltati.";
}
return response()->download($zipPath, 'documenti.zip')->deleteFileAfterSend(true)
->with('success', $message);
}
public function massDestroy(Request $request)
@@ -659,18 +740,20 @@ class DocumentoController extends Controller
$documenti = Documento::whereIn('id', $ids)->get();
foreach ($documenti as $documento) {
if ($documento->repository_id) {
$repo = StorageRepository::find($documento->repository_id);
if ($repo) {
$filesystem = $this->repoService->buildFilesystem($repo);
if ($filesystem && $filesystem->fileExists($documento->file_path)) {
$filesystem->delete($documento->file_path);
if (!$documento->isLink()) {
if ($documento->repository_id) {
$repo = StorageRepository::find($documento->repository_id);
if ($repo) {
$filesystem = $this->repoService->buildFilesystem($repo);
if ($filesystem && $filesystem->fileExists($documento->file_path)) {
$filesystem->delete($documento->file_path);
}
}
} else {
$disk = $this->resolveStorageDisk($documento);
if (Storage::disk($disk)->exists($documento->file_path)) {
Storage::disk($disk)->delete($documento->file_path);
}
}
} else {
$disk = $this->resolveStorageDisk($documento);
if (Storage::disk($disk)->exists($documento->file_path)) {
Storage::disk($disk)->delete($documento->file_path);
}
}
$documento->delete();
+37 -10
View File
@@ -162,14 +162,18 @@ class EmailController extends Controller
$imapSuccess = false;
$imapError = null;
$attachments = $this->processAttachments($request);
$attachmentResult = $this->processAttachments($request);
$attachmentPaths = $attachmentResult['paths'];
$linkDocs = $attachmentResult['links'];
$firmaId = $request->firma_id;
$body = $this->appendLinksToBody($validated['body'], $linkDocs);
foreach ($recipients as $recipient) {
try {
$bodyWithSig = $this->appendSignature($validated['body'], $firmaId, $settings);
$this->sendViaImap($settings, $recipient, $validated['subject'], $bodyWithSig, $request->cc, $attachments);
$bodyWithSig = $this->appendSignature($body, $firmaId, $settings);
$this->sendViaImap($settings, $recipient, $validated['subject'], $bodyWithSig, $request->cc, $attachmentPaths);
$imapSuccess = true;
} catch (\Exception $e) {
$imapError = $e->getMessage();
@@ -178,7 +182,7 @@ class EmailController extends Controller
}
try {
$bodyWithSig = $this->appendSignature($validated['body'], $firmaId, $settings);
$bodyWithSig = $this->appendSignature($body, $firmaId, $settings);
$this->storeSentMessage($settings, $recipients, $validated['subject'], $bodyWithSig, $request->cc);
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error('Store sent message error', ['error' => $e->getMessage()]);
@@ -195,10 +199,13 @@ class EmailController extends Controller
private function sendWithSender(Request $request, array $validated, array $recipients)
{
$sender = SenderAccount::findOrFail($request->mittente_id);
$attachmentPaths = $this->resolveAttachmentPaths($request);
$attachmentResult = $this->resolveAttachmentPaths($request);
$attachmentPaths = $attachmentResult['paths'];
$linkDocs = $attachmentResult['links'];
$settings = EmailSetting::getActive();
$body = $this->appendSignature($validated['body'], $request->firma_id, $settings);
$body = $this->appendLinksToBody($validated['body'], $linkDocs);
$body = $this->appendSignature($body, $request->firma_id, $settings);
$ok = 0;
$errors = [];
@@ -583,6 +590,7 @@ class EmailController extends Controller
private function processAttachments(Request $request): array
{
$paths = [];
$links = [];
if ($request->hasFile('allegati')) {
foreach ($request->file('allegati') as $file) {
@@ -597,18 +605,21 @@ class EmailController extends Controller
$ids = is_array($documentIds) ? $documentIds : explode(',', $documentIds);
$docs = Documento::whereIn('id', $ids)->get();
foreach ($docs as $doc) {
if ($doc->file_path && Storage::exists($doc->file_path)) {
if ($doc->isLink()) {
$links[] = ['nome' => $doc->nome_file, 'url' => $doc->url];
} elseif ($doc->file_path && Storage::exists($doc->file_path)) {
$paths[] = Storage::path($doc->file_path);
}
}
}
return $paths;
return ['paths' => $paths, 'links' => $links];
}
private function resolveAttachmentPaths(Request $request): array
{
$paths = [];
$links = [];
if ($request->hasFile('allegati')) {
foreach ($request->file('allegati') as $file) {
@@ -623,13 +634,29 @@ class EmailController extends Controller
$ids = is_array($documentIds) ? $documentIds : explode(',', $documentIds);
$docs = Documento::whereIn('id', $ids)->get();
foreach ($docs as $doc) {
if ($doc->file_path && \Illuminate\Support\Facades\Storage::exists($doc->file_path)) {
if ($doc->isLink()) {
$links[] = ['nome' => $doc->nome_file, 'url' => $doc->url];
} elseif ($doc->file_path && \Illuminate\Support\Facades\Storage::exists($doc->file_path)) {
$paths[] = \Illuminate\Support\Facades\Storage::path($doc->file_path);
}
}
}
return $paths;
return ['paths' => $paths, 'links' => $links];
}
private function appendLinksToBody(string $body, array $linkDocs): string
{
if (empty($linkDocs)) {
return $body;
}
$linksText = "\n\n---\nDocumenti collegati:\n";
foreach ($linkDocs as $link) {
$linksText .= "- {$link['nome']}: {$link['url']}\n";
}
return $body . $linksText;
}
private function storeSentMessage($settings, $recipients, $subject, $body, $cc = null)
+6 -2
View File
@@ -37,6 +37,7 @@ class GruppoController extends Controller
$allColumnDefs = [
['key' => 'nome', 'label' => 'Nome'],
['key' => 'anno_fondazione', 'label' => 'Anno Fondazione'],
['key' => 'descrizione', 'label' => 'Descrizione'],
['key' => 'diocesi', 'label' => 'Diocesi'],
['key' => 'livello', 'label' => 'Livello'],
@@ -155,6 +156,7 @@ class GruppoController extends Controller
$this->authorizeWrite('gruppi');
$data = $request->validate([
'nome' => 'required|string|max:255',
'anno_fondazione' => 'nullable|integer|min:1800|max:' . (date('Y') + 1),
'descrizione' => 'nullable|string',
'parent_id' => 'nullable|exists:gruppi,id',
'diocesi_ids' => 'nullable|array',
@@ -244,6 +246,7 @@ class GruppoController extends Controller
$gruppo = Gruppo::findOrFail($id);
$data = $request->validate([
'nome' => 'required|string|max:255',
'anno_fondazione' => 'nullable|integer|min:1800|max:' . (date('Y') + 1),
'descrizione' => 'nullable|string',
'parent_id' => 'nullable|exists:gruppi,id',
'diocesi_ids' => 'nullable|array',
@@ -478,6 +481,7 @@ class GruppoController extends Controller
try {
Gruppo::create([
'nome' => $nome,
'anno_fondazione' => !empty($data['anno_fondazione']) ? (int) $data['anno_fondazione'] : null,
'descrizione' => $data['descrizione'] ?? null,
'parent_id' => !empty($data['parent_id']) ? (int) $data['parent_id'] : null,
'diocesi_id' => !empty($data['diocesi_id']) ? (int) $data['diocesi_id'] : null,
@@ -512,8 +516,8 @@ class GruppoController extends Controller
public function downloadTemplate()
{
$this->authorizeRead('gruppi');
$headers = ['nome', 'descrizione', 'parent_id', 'diocesi_id', 'indirizzo_incontro', 'cap_incontro', 'città_incontro', 'sigla_provincia_incontro'];
$example = ['Gruppo Giovani', 'Giovani dai 18 ai 30 anni', '', '', 'Via Roma 10', '00100', 'Roma', 'RM'];
$headers = ['nome', 'anno_fondazione', 'descrizione', 'parent_id', 'diocesi_id', 'indirizzo_incontro', 'cap_incontro', 'città_incontro', 'sigla_provincia_incontro'];
$example = ['Gruppo Giovani', '2020', 'Giovani dai 18 ai 30 anni', '', '', 'Via Roma 10', '00100', 'Roma', 'RM'];
$csv = implode(',', $headers) . "\n" . implode(',', $example);
+28 -7
View File
@@ -120,7 +120,9 @@ class MailingController extends Controller
return back()->with('error', 'Nessun destinatario email valido trovato.')->withInput();
}
$attachmentPaths = $this->resolveMailingAttachmentPaths($documentiAllegati);
$attachmentResult = $this->resolveMailingAttachmentPaths($documentiAllegati);
$attachmentPaths = $attachmentResult['paths'];
$linkDocs = $attachmentResult['links'];
$firmaId = $data['firma_id'] ?? null;
if (!$firmaId && !empty($data['lista_id'])) {
@@ -128,7 +130,7 @@ class MailingController extends Controller
$firmaId = $lista?->firma?->id;
}
$corpoConFirma = $data['corpo'];
$corpoConFirma = $this->appendMailingLinksToBody($data['corpo'], $linkDocs);
if ($firmaId) {
$firma = Firma::find($firmaId);
if ($firma && $firma->contenuto_html) {
@@ -284,14 +286,16 @@ class MailingController extends Controller
return back()->with('error', 'Nessun destinatario email valido trovato nelle liste selezionate.');
}
$attachmentPaths = $this->resolveMailingAttachmentPaths($documentiAllegati);
$attachmentResult = $this->resolveMailingAttachmentPaths($documentiAllegati);
$attachmentPaths = $attachmentResult['paths'];
$linkDocs = $attachmentResult['links'];
$firmaId = $data['firma_id'] ?? null;
if (!$firmaId && $listeModels->count() === 1) {
$firmaId = $listeModels->first()->firma_id;
}
$corpoConFirma = $data['corpo'];
$corpoConFirma = $this->appendMailingLinksToBody($data['corpo'], $linkDocs);
if ($firmaId) {
$firma = Firma::find($firmaId);
if ($firma && $firma->contenuto_html) {
@@ -419,17 +423,34 @@ class MailingController extends Controller
private function resolveMailingAttachmentPaths(array $documentIds): array
{
$paths = [];
$links = [];
if (empty($documentIds)) {
return $paths;
return ['paths' => $paths, 'links' => $links];
}
$docs = Documento::whereIn('id', $documentIds)->get();
foreach ($docs as $doc) {
if ($doc->file_path && Storage::exists($doc->file_path)) {
if ($doc->isLink()) {
$links[] = ['nome' => $doc->nome_file, 'url' => $doc->url];
} elseif ($doc->file_path && Storage::exists($doc->file_path)) {
$paths[] = Storage::path($doc->file_path);
}
}
return $paths;
return ['paths' => $paths, 'links' => $links];
}
private function appendMailingLinksToBody(string $body, array $linkDocs): string
{
if (empty($linkDocs)) {
return $body;
}
$linksText = "\n\n---\nDocumenti collegati:\n";
foreach ($linkDocs as $link) {
$linksText .= "- {$link['nome']}: {$link['url']}\n";
}
return $body . $linksText;
}
}
+6 -1
View File
@@ -18,9 +18,14 @@ class Documento extends Model
'tenant_id', 'user_id', 'cartella_id', 'repository_id', 'storage_disk',
'nome_file', 'file_path', 'tipo', 'tipologia',
'visibilita', 'visibilita_target_id', 'visibilita_target_type',
'mime_type', 'dimensione', 'note'
'mime_type', 'dimensione', 'note', 'url'
];
public function isLink(): bool
{
return $this->tipo === 'link';
}
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class);
+2 -1
View File
@@ -15,13 +15,14 @@ class Gruppo extends Model
protected $table = 'gruppi';
protected $fillable = [
'tenant_id', 'parent_id', 'diocesi_id', 'responsabile_ids',
'nome', 'descrizione', 'indirizzo_incontro', 'cap_incontro',
'nome', 'anno_fondazione', 'descrizione', 'indirizzo_incontro', 'cap_incontro',
'città_incontro', 'sigla_provincia_incontro', 'mappa_posizione', 'metadata'
];
protected $casts = [
'metadata' => 'array',
'responsabile_ids' => 'array',
'anno_fondazione' => 'integer',
];
protected $appends = ['email_primaria', 'telefono_primario'];
@@ -13,10 +13,10 @@ return new class extends Migration
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome_file');
$table->string('file_path');
$table->string('file_path')->nullable();
$table->string('tipo')->nullable();
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro'])->default('documento');
$table->enum('visibilita', ['pubblico', 'gruppo', 'individuo', 'associazione', 'federazione'])->default('gruppo');
$table->string('tipologia', 50)->default('documento');
$table->string('visibilita', 50)->default('gruppo');
$table->unsignedBigInteger('visibilita_target_id')->nullable();
$table->string('visibilita_target_type')->nullable();
$table->string('mime_type')->nullable();
@@ -1,22 +1,19 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('documenti', function (Blueprint $table) {
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro', 'email_attachment'])->default('documento')->change();
});
// Column type changed to string in 2024_01_01_000008_create_documenti_table.php.
// Application-level validation handles allowed values.
}
public function down(): void
{
Schema::table('documenti', function (Blueprint $table) {
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro'])->default('documento')->change();
});
// No-op: column is string, not enum.
}
};
@@ -8,10 +8,13 @@ return new class extends Migration
{
public function up(): void
{
Schema::table('gruppo_individuo', function (Blueprint $table) {
$table->dropForeign(['ruolo_id']);
$table->dropColumn('ruolo_id');
});
if (Schema::hasColumn('gruppo_individuo', 'ruolo_id')) {
Schema::table('gruppo_individuo', function (Blueprint $table) {
$table->dropIndex(['ruolo_id']);
$table->dropForeign(['ruolo_id']);
$table->dropColumn('ruolo_id');
});
}
if (!Schema::hasColumn('gruppo_individuo', 'ruolo_ids')) {
Schema::table('gruppo_individuo', function (Blueprint $table) {
@@ -1,17 +1,19 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
DB::statement("ALTER TABLE documenti MODIFY COLUMN tipologia ENUM('avatar', 'galleria', 'documento', 'statuto', 'altro', 'programma', 'locandina') DEFAULT 'documento'");
// Column type changed to string in 2024_01_01_000008_create_documenti_table.php.
// Application-level validation handles allowed values.
}
public function down(): void
{
DB::statement("ALTER TABLE documenti MODIFY COLUMN tipologia ENUM('avatar', 'galleria', 'documento', 'statuto', 'altro') DEFAULT 'documento'");
// No-op: column is string, not enum.
}
};
@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (!Schema::hasColumn('gruppi', 'anno_fondazione')) {
Schema::table('gruppi', function (Blueprint $table) {
$table->year('anno_fondazione')->nullable()->after('nome');
});
}
}
public function down(): void
{
if (Schema::hasColumn('gruppi', 'anno_fondazione')) {
Schema::table('gruppi', function (Blueprint $table) {
$table->dropColumn('anno_fondazione');
});
}
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('documenti', function (Blueprint $table) {
$table->text('url')->nullable()->after('note');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('documenti', function (Blueprint $table) {
$table->dropColumn('url');
});
}
};
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (Schema::hasColumn('documenti', 'file_path') && DB::getDriverName() === 'mysql') {
DB::statement('ALTER TABLE documenti MODIFY COLUMN file_path VARCHAR(255) NULL');
}
}
public function down(): void
{
if (Schema::hasColumn('documenti', 'file_path') && DB::getDriverName() === 'mysql') {
DB::statement('ALTER TABLE documenti MODIFY COLUMN file_path VARCHAR(255) NOT NULL');
}
}
};
-209
View File
@@ -1,209 +0,0 @@
#!/bin/bash
# post-deploy.sh — Generated by build-dist.sh
# Usage: bash post-deploy.sh {check|upgrade|install} [--yes]
set -u
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
ARTISAN="php ${APP_DIR}/artisan"
DIAGNOSE="php ${APP_DIR}/diagnose.php"
RED='\033[31m'; GREEN='\033[32m'; YELLOW='\033[33m'
CYAN='\033[36m'; BOLD='\033[1m'; NC='\033[0m'
MODE="${1:-check}"
BATCH=false; [ "${2:-}" = "--yes" ] && BATCH=true
echo -e "${CYAN}${BOLD}============================================${NC}"
echo -e "${CYAN}${BOLD} Glastree Post-Deploy — ${MODE}${NC}"
echo -e "${CYAN}${BOLD}============================================${NC}"
echo ""
IS_FRESH=false; [ ! -f "${APP_DIR}/.env" ] && IS_FRESH=true
log() { echo -e " ${BOLD}$1${NC}"; }
ok() { echo -e " ${GREEN}$1${NC}"; }
warn() { echo -e " ${YELLOW}$1${NC}"; }
fail() { echo -e " ${RED}$1${NC}"; }
check_artisan() {
if [ ! -f "${APP_DIR}/artisan" ]; then
fail "artisan non trovato. Esegui da root dell'applicazione."
exit 1
fi
}
run_step() {
local step="$1" desc="$2"; shift 2
log "${step} ${desc}"
if "$@" 2>&1 | sed 's/^/ /'; then
ok "${desc}"
return 0
else
local rc=$?
fail "${desc} — exit code ${rc}"
return ${rc}
fi
}
run_diagnose() {
if ${DIAGNOSE} 2>&1 | tail -5; then
ok "Diagnostica OK"
else
warn "Diagnostica ha rilevato problemi (non bloccante)"
fi
}
run_check() {
echo -e "${YELLOW}═══════════ CONTROLLO ═══════════${NC}" ; echo ""
echo -e "${BOLD}[0] Diagnostica sistema${NC}"
run_diagnose ; echo ""
echo -e "${BOLD}[1] Ambiente${NC}"
if $IS_FRESH; then warn ".env non trovato — fresh install necessario"
else ok ".env presente"
fi ; echo ""
echo -e "${BOLD}[2] Migration pendenti${NC}"
if [ -f "${APP_DIR}/artisan" ]; then
$ARTISAN migrate:status 2>&1 | grep -E '(Pending|Ran)' | head -5 || true
PENDING=$(php -r "echo trim(shell_exec('${ARTISAN} migrate:status 2>&1 | grep -c Pending || true'));" 2>/dev/null || echo "0")
if [ "${PENDING:-0}" -gt 0 ] 2>/dev/null; then
warn "${PENDING} migration da eseguire"
else
ok "Nessuna migration pendente"
fi
else
warn "artisan non disponibile — skip migration check"
fi ; echo ""
echo -e "${BOLD}[3] Storage & Permessi${NC}"
for d in storage/framework/cache/data storage/framework/sessions \
storage/framework/views storage/logs bootstrap/cache \
storage/app/public storage/app/backups; do
p="${APP_DIR}/${d}"
if [ -d "$p" ]; then
[ -w "$p" ] && ok "${d}" || warn "${d} — non scrivibile"
else
warn "${d} — mancante"
fi
done ; echo ""
echo -e "${BOLD}[4] Symlink storage${NC}"
if [ -L "${APP_DIR}/public/storage" ]; then
ok "public/storage → $(readlink "${APP_DIR}/public/storage")"
else
warn "public/storage non presente"
fi ; echo ""
echo -e "${BOLD}[5] Cache${NC}"
for d in bootstrap/cache storage/framework/cache/data \
storage/framework/sessions storage/framework/views; do
c=$(find "${APP_DIR}/${d}" -type f 2>/dev/null | wc -l)
[ "$c" -gt 0 ] && warn "${d}: ${c} file" || ok "${d}: vuoto"
done
echo "" ; echo -e "${YELLOW}═══════════ FINE CONTROLLO ═══════════${NC}"
}
run_upgrade() {
echo -e "${YELLOW}═══════════ UPGRADE ═══════════${NC}" ; check_artisan
run_step "[1/9]" "Directory storage" mkdir -p \
"${APP_DIR}/storage/framework/cache/data" \
"${APP_DIR}/storage/framework/sessions" \
"${APP_DIR}/storage/framework/views" \
"${APP_DIR}/storage/logs" "${APP_DIR}/bootstrap/cache" \
"${APP_DIR}/storage/app/public" \
"${APP_DIR}/storage/app/public/logos" \
"${APP_DIR}/storage/app/public/documenti/eventi" \
"${APP_DIR}/storage/app/backups" \
"${APP_DIR}/storage/app/documenti" \
"${APP_DIR}/storage/app/private/avatars/gruppi" || true
run_step "[2/9]" ".gitignore storage" bash -c \
"echo '*'>'${APP_DIR}/storage/app/public/.gitignore'; echo '!.gitignore'>>'${APP_DIR}/storage/app/public/.gitignore'" || true
run_step "[3/9]" "Permessi" chmod -R 775 \
"${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache" || true
run_step "[4/9]" "Symlink storage" bash -c \
"rm -f '${APP_DIR}/public/storage'; ln -sf '../storage/app/public' '${APP_DIR}/public/storage'" || true
run_step "[5/10]" "Migrazioni DB" $ARTISAN migrate --force || true
run_step "[6/10]" "Seed diocesi" $ARTISAN db:seed --class=DiocesiSeeder --force || true
run_step "[7/10]" "Dump autoload" composer dump-autoload --no-dev --optimize || true
run_step "[8/10]" "Key generate" $ARTISAN key:generate --force || true
run_step "[9/10]" "Pulizia cache" bash -c \
"$ARTISAN config:clear && $ARTISAN route:clear && $ARTISAN view:clear" || true
run_step "[10/10]" "Verifica finale" $DIAGNOSE || true
echo "" ; echo -e "${GREEN}${BOLD}✅ Upgrade completato.${NC}"
}
run_install() {
echo -e "${YELLOW}═══════════ INSTALL (fresh) ═══════════${NC}" ; check_artisan
run_step "[1/10]" "Directory storage" mkdir -p \
"${APP_DIR}/storage/framework/cache/data" \
"${APP_DIR}/storage/framework/sessions" \
"${APP_DIR}/storage/framework/views" \
"${APP_DIR}/storage/logs" "${APP_DIR}/bootstrap/cache" \
"${APP_DIR}/storage/app/public" \
"${APP_DIR}/storage/app/public/logos" \
"${APP_DIR}/storage/app/public/documenti/eventi" \
"${APP_DIR}/storage/app/backups" \
"${APP_DIR}/storage/app/documenti" \
"${APP_DIR}/storage/app/private/avatars/gruppi" || true
run_step "[2/10]" ".gitignore storage" bash -c \
"echo '*'>'${APP_DIR}/storage/app/public/.gitignore'; echo '!.gitignore'>>'${APP_DIR}/storage/app/public/.gitignore'" || true
run_step "[3/10]" "Permessi" chmod -R 775 \
"${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache" || true
run_step "[4/10]" "File .env" cp "${APP_DIR}/.env.example" "${APP_DIR}/.env" || true
warn "MODIFICA .env con i tuoi dati DB/URL prima di proseguire"
if ! $BATCH; then read -p " Fatto? (Invio per continuare) " _; fi
run_step "[5/10]" "Symlink storage" bash -c \
"rm -f '${APP_DIR}/public/storage'; ln -sf '../storage/app/public' '${APP_DIR}/public/storage'" || true
run_step "[6/10]" "Dump autoload" composer dump-autoload --no-dev --optimize || true
run_step "[7/10]" "Key generate" $ARTISAN key:generate --force || true
run_step "[8/11]" "Migrazioni DB" $ARTISAN migrate --force || true
run_step "[9/11]" "Seed diocesi" $ARTISAN db:seed --class=DiocesiSeeder --force || true
run_step "[10/11]" "Pulizia cache" bash -c \
"$ARTISAN config:clear && $ARTISAN route:clear && $ARTISAN view:clear" || true
run_step "[11/11]" "Verifica finale" $DIAGNOSE || true
echo "" ; echo -e "${GREEN}${BOLD}✅ Installazione completata.${NC}"
}
case "$MODE" in
check) run_check ;;
upgrade)
run_check ; echo ""
if $BATCH; then run_upgrade
else
read -p "Procedere con l'upgrade? (s/N) " c
[ "$c" = "s" ] || [ "$c" = "S" ] && run_upgrade || echo "Annullato."
fi
;;
install) run_install ;;
*)
echo "Uso: bash post-deploy.sh {check|upgrade|install} [--yes]"
echo " check — diagnostica (nessuna modifica)"
echo " upgrade — aggiorna installazione esistente"
echo " install — fresh install"
echo " --yes — batch (nessuna conferma)"
exit 1
;;
esac
+24 -1
View File
@@ -16,6 +16,13 @@
<label>Nome *</label>
<input type="text" name="nome_file" class="form-control" value="{{ old('nome_file', $documento->nome_file) }}" required>
</div>
@if($documento->isLink())
<div class="form-group">
<label>URL esterno</label>
<input type="url" name="url" class="form-control" value="{{ old('url', $documento->url) }}" placeholder="https://esempio.com/documento.pdf">
<small class="text-muted">Modifica l'indirizzo web del documento</small>
</div>
@endif
<div class="form-group">
<label>Tipologia *</label>
<select name="tipologia" class="form-control" required>
@@ -72,6 +79,12 @@
</div>
<div class="card-body">
<table class="table table-sm table-borderless">
@if($documento->isLink())
<tr>
<td style="width: 100px;"><strong>URL:</strong></td>
<td><a href="{{ $documento->url }}" target="_blank" class="text-truncate d-inline-block" style="max-width: 200px;">{{ $documento->url }}</a></td>
</tr>
@else
<tr>
<td style="width: 100px;"><strong>File:</strong></td>
<td>{{ $documento->file_path }}</td>
@@ -84,6 +97,11 @@
<td><strong>Dimensione:</strong></td>
<td>{{ $documento->dimensione ? number_format($documento->dimensione / 1024, 1) . ' KB' : '-' }}</td>
</tr>
@endif
<tr>
<td><strong>Tipo:</strong></td>
<td>@if($documento->isLink()) <span class="badge badge-info">Link esterno</span> @else Upload @endif</td>
</tr>
<tr>
<td><strong>Creato:</strong></td>
<td>{{ $documento->created_at->format('d/m/Y H:i') }}</td>
@@ -95,7 +113,12 @@
</tr>
@endif
</table>
@if($documento->file_path)
@if($documento->isLink())
<hr>
<a href="{{ $documento->url }}" target="_blank" class="btn btn-info btn-block">
<i class="fas fa-external-link-alt mr-1"></i> Apri link esterno
</a>
@elseif($documento->file_path)
<hr>
<a href="/documenti/{{ $documento->id }}/download" class="btn btn-primary btn-block">
<i class="fas fa-download mr-1"></i> Scarica
+63 -5
View File
@@ -234,7 +234,11 @@ $tableColumnsJson = json_encode($tableColumns ?? []);
<a href="/documenti/{{ $documento->id }}/edit">{{ $documento->nome_file }}</a>
</h6>
<div class="text-center py-2" style="font-size: 2.5rem;">
<i class="fas {{ $icon }}"></i>
@if($documento->isLink())
<i class="fas fa-link text-info"></i>
@else
<i class="fas {{ $icon }}"></i>
@endif
</div>
<div class="text-center mb-2">
<span class="badge badge-{{ $documento->tipologia === 'avatar' ? 'primary' : ($documento->tipologia === 'galleria' ? 'success' : 'secondary') }}">
@@ -259,7 +263,11 @@ $tableColumnsJson = json_encode($tableColumns ?? []);
</div>
</div>
<div class="card-footer p-2 text-center">
@if($documento->file_path)
@if($documento->isLink())
<a href="{{ $documento->url }}" target="_blank" class="btn btn-xs btn-info" title="Apri link esterno">
<i class="fas fa-external-link-alt"></i>
</a>
@elseif($documento->file_path)
<button type="button" class="btn btn-xs btn-primary" onclick="previewDoc({{ $documento->id }}, '{{ $documento->mime_type }}')" title="Anteprima">
<i class="fas fa-eye"></i>
</button>
@@ -271,9 +279,11 @@ $tableColumnsJson = json_encode($tableColumns ?? []);
<a href="/documenti/{{ $documento->id }}/edit" class="btn btn-xs btn-warning" title="Modifica">
<i class="fas fa-edit"></i>
</a>
@if(!$documento->isLink())
<button type="button" class="btn btn-xs btn-secondary" onclick="showMoveModal({{ $documento->id }}, '{{ e($documento->nome_file) }}')" title="Sposta in altra cartella">
<i class="fas fa-folder-open"></i>
</button>
@endif
<button type="button" class="btn btn-xs btn-info" onclick="showAssociateModal({{ $documento->id }})" title="Associa a...">
<i class="fas fa-link"></i>
</button>
@@ -383,7 +393,11 @@ $tableColumnsJson = json_encode($tableColumns ?? []);
</td>
@endif
<td>
<i class="fas {{ $icon }} mr-2"></i>
@if($documento->isLink())
<i class="fas fa-link text-info mr-2"></i>
@else
<i class="fas {{ $icon }} mr-2"></i>
@endif
<span class="font-weight-bold">{{ $documento->nome_file }}</span>
<div class="small text-muted">
@if($documento->user){{ $documento->user->name }}@endif
@@ -460,7 +474,11 @@ $tableColumnsJson = json_encode($tableColumns ?? []);
</td>
<td class="small">{{ $documento->created_at->format('d/m/Y') }}</td>
<td>
@if($documento->file_path)
@if($documento->isLink())
<a href="{{ $documento->url }}" target="_blank" class="btn btn-xs btn-info" title="Apri link esterno">
<i class="fas fa-external-link-alt"></i>
</a>
@elseif($documento->file_path)
<button type="button" class="btn btn-xs btn-primary" onclick="previewDoc({{ $documento->id }}, '{{ $documento->mime_type }}')" title="Anteprima">
<i class="fas fa-eye"></i>
</button>
@@ -472,9 +490,11 @@ $tableColumnsJson = json_encode($tableColumns ?? []);
<a href="/documenti/{{ $documento->id }}/edit" class="btn btn-xs btn-warning" title="Modifica">
<i class="fas fa-edit"></i>
</a>
@if(!$documento->isLink())
<button type="button" class="btn btn-xs btn-secondary" onclick="showMoveModal({{ $documento->id }}, '{{ e($documento->nome_file) }}')" title="Sposta in altra cartella">
<i class="fas fa-folder-open"></i>
</button>
@endif
<button type="button" class="btn btn-xs btn-info" onclick="showAssociateModal({{ $documento->id }})" title="Associa a...">
<i class="fas fa-link"></i>
</button>
@@ -631,10 +651,28 @@ $tableColumnsJson = json_encode($tableColumns ?? []);
</div>
@endif
<div class="form-group">
<label>Tipo caricamento</label>
<div class="btn-group btn-group-toggle d-block" data-toggle="buttons">
<label class="btn btn-outline-primary active" id="tipoFileLabel">
<input type="radio" name="tipo_caricamento" value="file" checked onchange="toggleTipoCaricamento()">
<i class="fas fa-upload mr-1"></i> File
</label>
<label class="btn btn-outline-info" id="tipoLinkLabel">
<input type="radio" name="tipo_caricamento" value="link" onchange="toggleTipoCaricamento()">
<i class="fas fa-link mr-1"></i> URL esterno
</label>
</div>
</div>
<div class="form-group" id="fileUploadGroup">
<label>File *</label>
<input type="file" name="file" class="form-control" required>
<input type="file" name="file" class="form-control" id="fileInput">
<small class="text-muted">Max 10MB</small>
</div>
<div class="form-group" id="urlUploadGroup" style="display:none;">
<label>URL esterno *</label>
<input type="url" name="url" class="form-control" id="urlInput" placeholder="https://esempio.com/documento.pdf">
<small class="text-muted">Inserisci l'indirizzo web del documento</small>
</div>
<div class="form-group" id="uploadFolderGroup">
<label>Cartella</label>
<select name="cartella_id" id="uploadCartellaId" class="form-control">
@@ -1165,6 +1203,26 @@ function previewDoc(id, mimeType) {
$('#previewModal').modal('show');
}
function toggleTipoCaricamento() {
var tipo = document.querySelector('input[name="tipo_caricamento"]:checked').value;
var fileGroup = document.getElementById('fileUploadGroup');
var urlGroup = document.getElementById('urlUploadGroup');
var fileInput = document.getElementById('fileInput');
var urlInput = document.getElementById('urlInput');
if (tipo === 'link') {
fileGroup.style.display = 'none';
urlGroup.style.display = 'block';
fileInput.removeAttribute('required');
urlInput.setAttribute('required', 'required');
} else {
fileGroup.style.display = 'block';
urlGroup.style.display = 'none';
fileInput.setAttribute('required', 'required');
urlInput.removeAttribute('required');
}
}
function loadUploadTargets() {
const visibilita = document.getElementById('uploadVisibilita').value;
const group = document.getElementById('uploadTargetGroup');
@@ -49,38 +49,63 @@
display: block;
margin-bottom: 4px;
}
.download-btn {
.btn {
display: inline-block;
padding: 10px 24px;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
font-weight: 500;
transition: background-color 0.2s;
}
.download-btn:hover {
.btn-primary {
background-color: #007bff;
}
.btn-primary:hover {
background-color: #0056b3;
color: white;
}
.btn-info {
background-color: #17a2b8;
}
.btn-info:hover {
background-color: #138496;
color: white;
}
</style>
</head>
<body>
<div class="fallback-container">
<div class="fallback-icon">📄</div>
<div class="fallback-title">Anteprima non disponibile</div>
<div class="fallback-message">
Questo tipo di file non può essere visualizzato nell'anteprima.
Scarica il file per visualizzarlo con l'applicazione appropriata.
</div>
<div class="fallback-file-info">
<strong>{{ $documento->nome_file }}</strong>
{{ strtoupper(pathinfo($documento->file_path, PATHINFO_EXTENSION)) }} &middot;
{{ number_format($documento->dimensione / 1024, 1) }} KB
</div>
<a href="/documenti/{{ $documento->id }}/download" class="download-btn">
⬇️ Scarica File
</a>
@if($documento->isLink())
<div class="fallback-icon">🔗</div>
<div class="fallback-title">Documento esterno</div>
<div class="fallback-message">
Questo è un collegamento a una risorsa esterna.
Clicca il pulsante qui sotto per aprirla.
</div>
<div class="fallback-file-info">
<strong>{{ $documento->nome_file }}</strong>
URL esterno
</div>
<a href="{{ $documento->url }}" target="_blank" class="btn btn-info">
Apri link esterno
</a>
@else
<div class="fallback-icon">📄</div>
<div class="fallback-title">Anteprima non disponibile</div>
<div class="fallback-message">
Questo tipo di file non può essere visualizzato nell'anteprima.
Scarica il file per visualizzarlo con l'applicazione appropriata.
</div>
<div class="fallback-file-info">
<strong>{{ $documento->nome_file }}</strong>
{{ $documento->file_path ? strtoupper(pathinfo($documento->file_path, PATHINFO_EXTENSION)) : '-' }} &middot;
{{ $documento->dimensione ? number_format($documento->dimensione / 1024, 1) . ' KB' : '-' }}
</div>
<a href="/documenti/{{ $documento->id }}/download" class="btn btn-primary">
Scarica File
</a>
@endif
</div>
</body>
</html>
+1 -1
View File
@@ -160,7 +160,7 @@
<label class="custom-control-label" for="doc-{{ $doc->id }}"></label>
</div>
</td>
<td>{{ $doc->nome_file }}</td>
<td>{{ $doc->nome_file }} @if($doc->isLink()) <span class="badge badge-info"><i class="fas fa-link mr-1"></i>URL</span> @endif</td>
<td><span class="badge badge-info">{{ $doc->tipologia ?: $doc->tipo }}</span></td>
</tr>
@endforeach
+8
View File
@@ -39,6 +39,14 @@
<span class="invalid-feedback">{{ $message }}</span>
@enderror
</div>
<div class="form-group">
<label>Anno Fondazione</label>
<input type="number" name="anno_fondazione" class="form-control @error('anno_fondazione') is-invalid @enderror"
value="{{ old('anno_fondazione') }}" min="1800" max="{{ date('Y') + 1 }}" step="1">
@error('anno_fondazione')
<span class="invalid-feedback">{{ $message }}</span>
@enderror
</div>
<div class="form-group">
<label>Gruppo Padre</label>
<select name="parent_id" class="form-control">
+8
View File
@@ -36,6 +36,14 @@
<label>Nome *</label>
<input type="text" name="nome" class="form-control" value="{{ old('nome', $gruppo->nome) }}" required>
</div>
<div class="form-group">
<label>Anno Fondazione</label>
<input type="number" name="anno_fondazione" class="form-control @error('anno_fondazione') is-invalid @enderror"
value="{{ old('anno_fondazione', $gruppo->anno_fondazione) }}" min="1800" max="{{ date('Y') + 1 }}" step="1">
@error('anno_fondazione')
<span class="invalid-feedback">{{ $message }}</span>
@enderror
</div>
<div class="form-group">
<label>Gruppo Padre</label>
<select name="parent_id" class="form-control">
+1
View File
@@ -49,6 +49,7 @@
<h6>Campi:</h6>
<ul>
<li><code>nome</code> (obbligatorio)</li>
<li><code>anno_fondazione</code> (anno, es. 2020)</li>
<li><code>descrizione</code></li>
<li><code>parent_id</code> (ID del gruppo padre)</li>
<li><code>diocesi_id</code> (ID della diocesi)</li>
+7
View File
@@ -7,6 +7,7 @@ $canWriteGruppi = Auth::user()->canManage('gruppi');
$canDeleteGruppi = Auth::user()->canDelete('gruppi');
$columnLabels = [
'nome' => 'Nome',
'anno_fondazione' => 'Anno Fond.',
'descrizione' => 'Descrizione',
'diocesi' => 'Diocesi',
'livello' => 'Livello',
@@ -104,6 +105,9 @@ $entityType = $entityType ?? 'gruppi';
@if(in_array('nome', $visibleColumns))
<th data-sortable="true" data-column="nome" onclick="sortTable('nome')" style="cursor:pointer;">Nome <i class="fas fa-sort float-right text-muted"></i></th>
@endif
@if(in_array('anno_fondazione', $visibleColumns))
<th style="width: 120px;" data-column="anno_fondazione">Anno Fond.</th>
@endif
@if(in_array('descrizione', $visibleColumns))
<th data-sortable="true" data-column="descrizione" onclick="sortTable('descrizione')" style="cursor:pointer;">Descrizione <i class="fas fa-sort float-right text-muted"></i></th>
@endif
@@ -157,6 +161,9 @@ $entityType = $entityType ?? 'gruppi';
</span>
</td>
@endif
@if(in_array('anno_fondazione', $visibleColumns))
<td>{{ $gruppo->anno_fondazione ?? '-' }}</td>
@endif
@if(in_array('descrizione', $visibleColumns))
<td>{{ Str::limit($gruppo->descrizione, 50) ?: '-' }}</td>
@endif
+4
View File
@@ -64,6 +64,10 @@ $canDeleteGruppi = Auth::user()->canDelete('gruppi');
@endif
</td>
</tr>
<tr>
<td><strong>Anno Fondazione:</strong></td>
<td>{{ $gruppo->anno_fondazione ?? '-' }}</td>
</tr>
</table>
</div>
<div class="col-md-6">
+1 -1
View File
@@ -116,7 +116,7 @@
<label class="custom-control-label" for="doc-{{ $doc->id }}"></label>
</div>
</td>
<td>{{ $doc->nome_file }}</td>
<td>{{ $doc->nome_file }} @if($doc->isLink()) <span class="badge badge-info"><i class="fas fa-link mr-1"></i>URL</span> @endif</td>
<td><span class="badge badge-info">{{ $doc->tipologia ?: $doc->tipo }}</span></td>
</tr>
@endforeach
+1 -1
View File
@@ -166,7 +166,7 @@
<label class="custom-control-label" for="doc-{{ $doc->id }}"></label>
</div>
</td>
<td>{{ $doc->nome_file }}</td>
<td>{{ $doc->nome_file }} @if($doc->isLink()) <span class="badge badge-info"><i class="fas fa-link mr-1"></i>URL</span> @endif</td>
<td><span class="badge badge-info">{{ $doc->tipologia ?: $doc->tipo }}</span></td>
<td>{{ $doc->created_at->format('d/m/Y') }}</td>
</tr>
+311
View File
@@ -0,0 +1,311 @@
<?php
declare(strict_types=1);
namespace Tests\Feature;
use App\Models\Documento;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class DocumentoLinkTest extends TestCase
{
use RefreshDatabase;
private User $admin;
protected function setUp(): void
{
parent::setUp();
$this->admin = User::factory()->create([
'is_admin' => true,
'permissions' => ['documenti' => User::LEVEL_FULL],
]);
}
public function test_admin_can_create_link_document(): void
{
$this->actingAs($this->admin);
$response = $this->post('/documenti', [
'nome_file' => 'Documento di prova',
'tipologia' => 'documento',
'tipo_caricamento' => 'link',
'url' => 'https://example.com/document.pdf',
'visibilita' => 'pubblico',
'tags' => [],
]);
$response->assertSessionHas('success');
$response->assertRedirect();
$this->assertDatabaseHas('documenti', [
'nome_file' => 'Documento di prova',
'tipo' => 'link',
'url' => 'https://example.com/document.pdf',
]);
}
public function test_create_link_requires_url(): void
{
$this->actingAs($this->admin);
$response = $this->post('/documenti', [
'nome_file' => 'Documento senza url',
'tipologia' => 'documento',
'tipo_caricamento' => 'link',
'url' => '',
'visibilita' => 'pubblico',
]);
$response->assertSessionHasErrors('url');
}
public function test_create_link_validates_url_format(): void
{
$this->actingAs($this->admin);
$response = $this->post('/documenti', [
'nome_file' => 'Documento url errato',
'tipologia' => 'documento',
'tipo_caricamento' => 'link',
'url' => 'not-a-valid-url',
'visibilita' => 'pubblico',
]);
$response->assertSessionHasErrors('url');
}
public function test_create_link_does_not_require_file(): void
{
$this->actingAs($this->admin);
$response = $this->post('/documenti', [
'nome_file' => 'Solo link',
'tipologia' => 'documento',
'tipo_caricamento' => 'link',
'url' => 'https://example.com/doc.pdf',
'visibilita' => 'pubblico',
]);
$response->assertSessionHas('success');
}
public function test_create_link_sets_tipo_to_link(): void
{
$this->actingAs($this->admin);
$this->post('/documenti', [
'nome_file' => 'Link test',
'tipologia' => 'documento',
'tipo_caricamento' => 'link',
'url' => 'https://example.com/doc.pdf',
'visibilita' => 'pubblico',
]);
$doc = Documento::where('nome_file', 'Link test')->first();
$this->assertNotNull($doc);
$this->assertEquals('link', $doc->tipo);
$this->assertTrue($doc->isLink());
$this->assertNull($doc->file_path);
$this->assertNull($doc->mime_type);
$this->assertNull($doc->dimensione);
$this->assertNull($doc->repository_id);
}
public function test_create_link_with_visibility_individuo(): void
{
$this->actingAs($this->admin);
$individuo = \App\Models\Individuo::create([
'nome' => 'Test',
'cognome' => 'Individuo',
]);
$this->post('/documenti', [
'nome_file' => 'Link per individuo',
'tipologia' => 'documento',
'tipo_caricamento' => 'link',
'url' => 'https://example.com/doc.pdf',
'visibilita' => 'individuo',
'visibilita_target_id' => $individuo->id,
]);
$doc = Documento::where('nome_file', 'Link per individuo')->first();
$this->assertNotNull($doc);
$this->assertEquals(\App\Models\Individuo::class, $doc->visibilita_target_type);
$this->assertEquals($individuo->id, $doc->visibilita_target_id);
}
public function test_create_link_with_visibility_gruppo(): void
{
$this->actingAs($this->admin);
$gruppo = \App\Models\Gruppo::create([
'nome' => 'Gruppo di test',
]);
$this->post('/documenti', [
'nome_file' => 'Link per gruppo',
'tipologia' => 'documento',
'tipo_caricamento' => 'link',
'url' => 'https://example.com/doc.pdf',
'visibilita' => 'gruppo',
'visibilita_target_id' => $gruppo->id,
]);
$doc = Documento::where('nome_file', 'Link per gruppo')->first();
$this->assertNotNull($doc);
$this->assertEquals(\App\Models\Gruppo::class, $doc->visibilita_target_type);
$this->assertEquals($gruppo->id, $doc->visibilita_target_id);
}
public function test_download_link_redirects_to_url(): void
{
$this->actingAs($this->admin);
$doc = Documento::create([
'nome_file' => 'Link download test',
'tipo' => 'link',
'url' => 'https://example.com/download.pdf',
'tipologia' => 'documento',
'visibilita' => 'pubblico',
'user_id' => $this->admin->id,
]);
$response = $this->get("/documenti/{$doc->id}/download");
$response->assertRedirect('https://example.com/download.pdf');
}
public function test_preview_link_returns_fallback_view(): void
{
$this->actingAs($this->admin);
$doc = Documento::create([
'nome_file' => 'Link preview test',
'tipo' => 'link',
'url' => 'https://example.com/preview.pdf',
'tipologia' => 'documento',
'visibilita' => 'pubblico',
'user_id' => $this->admin->id,
]);
$response = $this->get("/documenti/{$doc->id}/preview");
$response->assertStatus(200);
$response->assertHeader('X-Preview-Fallback', 'true');
$response->assertSee('Apri link esterno');
}
public function test_update_link_url(): void
{
$this->actingAs($this->admin);
$doc = Documento::create([
'nome_file' => 'Link update test',
'tipo' => 'link',
'url' => 'https://example.com/old.pdf',
'tipologia' => 'documento',
'visibilita' => 'pubblico',
'user_id' => $this->admin->id,
]);
$response = $this->put("/documenti/{$doc->id}", [
'nome_file' => 'Link update test',
'tipologia' => 'documento',
'url' => 'https://example.com/new.pdf',
'note' => 'Aggiornato',
]);
$response->assertSessionHas('success');
$doc->refresh();
$this->assertEquals('https://example.com/new.pdf', $doc->url);
}
public function test_update_link_preserves_url_when_not_sent(): void
{
$this->actingAs($this->admin);
$doc = Documento::create([
'nome_file' => 'Link preserve url test',
'tipo' => 'link',
'url' => 'https://example.com/preserve.pdf',
'tipologia' => 'documento',
'visibilita' => 'pubblico',
'user_id' => $this->admin->id,
]);
$response = $this->put("/documenti/{$doc->id}", [
'nome_file' => 'Link preserve url test',
'tipologia' => 'documento',
]);
$response->assertSessionHas('success');
$doc->refresh();
$this->assertEquals('https://example.com/preserve.pdf', $doc->url);
}
public function test_update_link_validates_url_format(): void
{
$this->actingAs($this->admin);
$doc = Documento::create([
'nome_file' => 'Link validate url',
'tipo' => 'link',
'url' => 'https://example.com/valid.pdf',
'tipologia' => 'documento',
'visibilita' => 'pubblico',
'user_id' => $this->admin->id,
]);
$response = $this->put("/documenti/{$doc->id}", [
'nome_file' => 'Link validate url',
'tipologia' => 'documento',
'url' => 'not-valid-url',
]);
$response->assertSessionHasErrors('url');
}
public function test_destroy_link_does_not_call_storage(): void
{
$this->actingAs($this->admin);
$doc = Documento::create([
'nome_file' => 'Link destroy test',
'tipo' => 'link',
'url' => 'https://example.com/destroy.pdf',
'tipologia' => 'documento',
'visibilita' => 'pubblico',
'user_id' => $this->admin->id,
]);
$this->delete("/documenti/{$doc->id}");
$this->assertDatabaseMissing('documenti', ['id' => $doc->id]);
}
public function test_mass_download_skips_links(): void
{
$this->actingAs($this->admin);
$linkDoc = Documento::create([
'nome_file' => 'Link in mass download',
'tipo' => 'link',
'url' => 'https://example.com/skipped.pdf',
'tipologia' => 'documento',
'visibilita' => 'pubblico',
'user_id' => $this->admin->id,
]);
$response = $this->post('/documenti/mass-download', [
'ids' => [(string) $linkDoc->id],
]);
$response->assertSessionHas('error');
$response->assertSessionHas('error', fn(string $msg) => str_contains($msg, 'Nessun file'));
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Models\Documento;
use Tests\TestCase;
class DocumentoLinkTest extends TestCase
{
public function test_is_link_returns_true_when_tipo_is_link(): void
{
$doc = new Documento(['tipo' => 'link']);
$this->assertTrue($doc->isLink());
}
public function test_is_link_returns_false_when_tipo_is_upload(): void
{
$doc = new Documento(['tipo' => 'upload']);
$this->assertFalse($doc->isLink());
}
public function test_is_link_returns_false_when_tipo_is_allegato(): void
{
$doc = new Documento(['tipo' => 'allegato']);
$this->assertFalse($doc->isLink());
}
public function test_is_link_returns_false_when_tipo_is_null(): void
{
$doc = new Documento();
$this->assertFalse($doc->isLink());
}
public function test_url_is_fillable(): void
{
$doc = new Documento(['url' => 'https://example.com/doc']);
$this->assertEquals('https://example.com/doc', $doc->url);
}
public function test_link_doc_has_no_file_fields(): void
{
$doc = new Documento([
'tipo' => 'link',
'url' => 'https://example.com/doc',
'file_path' => null,
'mime_type' => null,
'dimensione' => null,
]);
$this->assertNull($doc->file_path);
$this->assertNull($doc->mime_type);
$this->assertNull($doc->dimensione);
$this->assertTrue($doc->isLink());
}
}
Vendored Executable
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../phpunit/phpunit/phpunit)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
$GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] = $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'] = array(realpath(__DIR__ . '/..'.'/phpunit/phpunit/phpunit'));
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = 'phpvfscomposer://'.$this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$data = str_replace('__DIR__', var_export(dirname($this->realpath), true), $data);
$data = str_replace('__FILE__', var_export($this->realpath, true), $data);
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
}
}
return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';
Vendored Executable
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../laravel/pint/builds/pint)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/laravel/pint/builds/pint');
}
}
return include __DIR__ . '/..'.'/laravel/pint/builds/pint';
File diff suppressed because it is too large Load Diff
+11 -4
View File
@@ -11,11 +11,11 @@ return array(
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php',
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
@@ -27,13 +27,15 @@ return array(
'2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php',
'606a39d89246991a373564698c2d8383' => $vendorDir . '/symfony/polyfill-php85/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'35a6ad97d21e794e7e22a17d806652e4' => $vendorDir . '/nunomaduro/termwind/src/Functions.php',
'b33e3d135e5d9e47d845c576147bda89' => $vendorDir . '/php-di/php-di/src/functions.php',
'1f87db08236948d07391152dccb70f04' => $vendorDir . '/google/apiclient-services/autoload.php',
'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php',
'09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php',
'a8d3953fd9959404dd22d3dfcd0a79f0' => $vendorDir . '/google/apiclient/src/aliases.php',
'23dd7ece5822da3d0100ef3deb0ef55f' => $vendorDir . '/laravel/agent-detector/src/functions.php',
'47e1160838b5e5a10346ac4084b58c23' => $vendorDir . '/laravel/prompts/src/helpers.php',
'35a6ad97d21e794e7e22a17d806652e4' => $vendorDir . '/nunomaduro/termwind/src/Functions.php',
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'801c31d8ed748cfa537fa45402288c95' => $vendorDir . '/psy/psysh/src/functions.php',
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
'9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php',
@@ -47,5 +49,10 @@ return array(
'91892b814db86b8442ad76273bb7aec5' => $vendorDir . '/laravel/framework/src/Illuminate/Reflection/helpers.php',
'493c6aea52f6009bab023b26c21a386a' => $vendorDir . '/laravel/framework/src/Illuminate/Support/functions.php',
'58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php',
'309cd39bb536ff667b25a3a76938cb83' => $vendorDir . '/laravel/pao/src/Autoload.php',
'c72349b1fe8d0deeedd3a52e8aa814d8' => $vendorDir . '/mockery/mockery/library/helpers.php',
'ce9671a430e4846b44e1c68c7611f9f5' => $vendorDir . '/mockery/mockery/library/Mockery.php',
'a1cfe24d14977df6878b9bf804af2d1c' => $vendorDir . '/nunomaduro/collision/src/Adapters/Phpunit/Autoload.php',
'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php',
'377b22b161c09ed6e5152de788ca020a' => $vendorDir . '/spatie/laravel-permission/src/helpers.php',
);
+12 -3
View File
@@ -10,8 +10,10 @@ return array(
'ZBateson\\StreamDecorators\\' => array($vendorDir . '/zbateson/stream-decorators/src'),
'ZBateson\\MbWrapper\\' => array($vendorDir . '/zbateson/mb-wrapper/src'),
'ZBateson\\MailMimeParser\\' => array($vendorDir . '/zbateson/mail-mime-parser/src'),
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
'Webklex\\PHPIMAP\\' => array($vendorDir . '/webklex/php-imap/src'),
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
'Tests\\' => array($baseDir . '/tests'),
'Termwind\\' => array($vendorDir . '/nunomaduro/termwind/src'),
'Symfony\\Polyfill\\Uuid\\' => array($vendorDir . '/symfony/polyfill-uuid'),
'Symfony\\Polyfill\\Php86\\' => array($vendorDir . '/symfony/polyfill-php86'),
@@ -64,8 +66,10 @@ return array(
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
'NunoMaduro\\Collision\\' => array($vendorDir . '/nunomaduro/collision/src'),
'Nette\\' => array($vendorDir . '/nette/schema/src', $vendorDir . '/nette/utils/src'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'Mockery\\' => array($vendorDir . '/mockery/mockery/library/Mockery'),
'League\\Uri\\' => array($vendorDir . '/league/uri', $vendorDir . '/league/uri-interfaces'),
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
'League\\Flysystem\\WebDAV\\' => array($vendorDir . '/league/flysystem-webdav'),
@@ -76,6 +80,9 @@ return array(
'Laravel\\Tinker\\' => array($vendorDir . '/laravel/tinker/src'),
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
'Laravel\\Prompts\\' => array($vendorDir . '/laravel/prompts/src'),
'Laravel\\Pao\\' => array($vendorDir . '/laravel/pao/src'),
'Laravel\\Pail\\' => array($vendorDir . '/laravel/pail/src'),
'Laravel\\AgentDetector\\' => array($vendorDir . '/laravel/agent-detector/src'),
'Invoker\\' => array($vendorDir . '/php-di/invoker/src'),
'Illuminate\\Support\\' => array($vendorDir . '/laravel/framework/src/Illuminate/Macroable', $vendorDir . '/laravel/framework/src/Illuminate/Collections', $vendorDir . '/laravel/framework/src/Illuminate/Conditionable', $vendorDir . '/laravel/framework/src/Illuminate/Reflection'),
'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'),
@@ -89,6 +96,7 @@ return array(
'Google\\' => array($vendorDir . '/google/apiclient/src'),
'Fruitcake\\Cors\\' => array($vendorDir . '/fruitcake/php-cors/src'),
'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
'Faker\\' => array($vendorDir . '/fakerphp/faker/src/Faker'),
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/src'),
@@ -96,8 +104,9 @@ return array(
'DirectoryTree\\ImapEngine\\Laravel\\' => array($vendorDir . '/directorytree/imapengine-laravel/src'),
'DirectoryTree\\ImapEngine\\' => array($vendorDir . '/directorytree/imapengine/src'),
'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'),
'Database\\Seeders\\' => array($baseDir . '/database/seeders'),
'Database\\Factories\\' => array($baseDir . '/database/factories'),
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
'Database\\Seeders\\' => array($baseDir . '/database/seeders', $vendorDir . '/laravel/pint/database/seeders'),
'Database\\Factories\\' => array($baseDir . '/database/factories', $vendorDir . '/laravel/pint/database/factories'),
'DI\\' => array($vendorDir . '/php-di/php-di/src'),
'Cron\\' => array($vendorDir . '/dragonmantank/cron-expression/src/Cron'),
'Carbon\\Doctrine\\' => array($vendorDir . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine'),
@@ -105,5 +114,5 @@ return array(
'Brick\\Math\\' => array($vendorDir . '/brick/math/src'),
'As247\\Flysystem\\GoogleDrive\\' => array($vendorDir . '/as247/flysystem-google-drive/src'),
'As247\\CloudStorages\\' => array($vendorDir . '/as247/cloud-storages/src'),
'App\\' => array($baseDir . '/app'),
'App\\' => array($baseDir . '/app', $vendorDir . '/laravel/pint/app'),
);
+2155 -4
View File
File diff suppressed because it is too large Load Diff
+2405 -2
View File
File diff suppressed because it is too large Load Diff
+318 -3
View File
@@ -3,11 +3,11 @@
'name' => 'laravel/laravel',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'def8d225449e5e84df57d03313c64e7b3e8d2834',
'reference' => 'f2ea5e160b020155947c05c7a1b676fd81c02742',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => false,
'dev' => true,
),
'versions' => array(
'as247/cloud-storages' => array(
@@ -46,6 +46,18 @@
'aliases' => array(),
'dev_requirement' => false,
),
'cordoval/hamcrest-php' => array(
'dev_requirement' => true,
'replaced' => array(
0 => '*',
),
),
'davedevelopment/hamcrest-php' => array(
'dev_requirement' => true,
'replaced' => array(
0 => '*',
),
),
'dflydev/dot-access-data' => array(
'pretty_version' => 'v3.0.3',
'version' => '3.0.3.0',
@@ -109,6 +121,24 @@
'aliases' => array(),
'dev_requirement' => false,
),
'fakerphp/faker' => array(
'pretty_version' => 'v1.24.1',
'version' => '1.24.1.0',
'reference' => 'e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5',
'type' => 'library',
'install_path' => __DIR__ . '/../fakerphp/faker',
'aliases' => array(),
'dev_requirement' => true,
),
'filp/whoops' => array(
'pretty_version' => '2.18.4',
'version' => '2.18.4.0',
'reference' => 'd2102955e48b9fd9ab24280a7ad12ed552752c4d',
'type' => 'library',
'install_path' => __DIR__ . '/../filp/whoops',
'aliases' => array(),
'dev_requirement' => true,
),
'firebase/php-jwt' => array(
'pretty_version' => 'v7.0.5',
'version' => '7.0.5.0',
@@ -199,6 +229,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'hamcrest/hamcrest-php' => array(
'pretty_version' => 'v2.1.1',
'version' => '2.1.1.0',
'reference' => 'f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487',
'type' => 'library',
'install_path' => __DIR__ . '/../hamcrest/hamcrest-php',
'aliases' => array(),
'dev_requirement' => true,
),
'illuminate/auth' => array(
'dev_requirement' => false,
'replaced' => array(
@@ -415,6 +454,21 @@
0 => 'v13.8.0',
),
),
'kodova/hamcrest-php' => array(
'dev_requirement' => true,
'replaced' => array(
0 => '*',
),
),
'laravel/agent-detector' => array(
'pretty_version' => 'v2.0.2',
'version' => '2.0.2.0',
'reference' => '90694b9256099591cf9e55d08c18ba7a00bf099f',
'type' => 'library',
'install_path' => __DIR__ . '/../laravel/agent-detector',
'aliases' => array(),
'dev_requirement' => true,
),
'laravel/framework' => array(
'pretty_version' => 'v13.8.0',
'version' => '13.8.0.0',
@@ -427,12 +481,39 @@
'laravel/laravel' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'def8d225449e5e84df57d03313c64e7b3e8d2834',
'reference' => 'f2ea5e160b020155947c05c7a1b676fd81c02742',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'laravel/pail' => array(
'pretty_version' => 'v1.2.6',
'version' => '1.2.6.0',
'reference' => 'aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf',
'type' => 'library',
'install_path' => __DIR__ . '/../laravel/pail',
'aliases' => array(),
'dev_requirement' => true,
),
'laravel/pao' => array(
'pretty_version' => 'v1.0.6',
'version' => '1.0.6.0',
'reference' => '02f62a64c2b60af44a418ee490fee193590d8269',
'type' => 'library',
'install_path' => __DIR__ . '/../laravel/pao',
'aliases' => array(),
'dev_requirement' => true,
),
'laravel/pint' => array(
'pretty_version' => 'v1.29.1',
'version' => '1.29.1.0',
'reference' => '0770e9b7fafd50d4586881d456d6eb41c9247a80',
'type' => 'project',
'install_path' => __DIR__ . '/../laravel/pint',
'aliases' => array(),
'dev_requirement' => true,
),
'laravel/prompts' => array(
'pretty_version' => 'v0.3.17',
'version' => '0.3.17.0',
@@ -532,6 +613,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'mockery/mockery' => array(
'pretty_version' => '1.6.12',
'version' => '1.6.12.0',
'reference' => '1f4efdd7d3beafe9807b08156dfcb176d18f1699',
'type' => 'library',
'install_path' => __DIR__ . '/../mockery/mockery',
'aliases' => array(),
'dev_requirement' => true,
),
'monolog/monolog' => array(
'pretty_version' => '3.10.0',
'version' => '3.10.0.0',
@@ -547,6 +637,15 @@
0 => '^1.0',
),
),
'myclabs/deep-copy' => array(
'pretty_version' => '1.13.4',
'version' => '1.13.4.0',
'reference' => '07d290f0c47959fd5eed98c95ee5602db07e0b6a',
'type' => 'library',
'install_path' => __DIR__ . '/../myclabs/deep-copy',
'aliases' => array(),
'dev_requirement' => true,
),
'nesbot/carbon' => array(
'pretty_version' => '3.11.4',
'version' => '3.11.4.0',
@@ -583,6 +682,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'nunomaduro/collision' => array(
'pretty_version' => 'v8.9.4',
'version' => '8.9.4.0',
'reference' => '716af8f95a470e9094cfca09ed897b023be191a5',
'type' => 'library',
'install_path' => __DIR__ . '/../nunomaduro/collision',
'aliases' => array(),
'dev_requirement' => true,
),
'nunomaduro/termwind' => array(
'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0',
@@ -592,6 +700,24 @@
'aliases' => array(),
'dev_requirement' => false,
),
'phar-io/manifest' => array(
'pretty_version' => '2.0.4',
'version' => '2.0.4.0',
'reference' => '54750ef60c58e43759730615a392c31c80e23176',
'type' => 'library',
'install_path' => __DIR__ . '/../phar-io/manifest',
'aliases' => array(),
'dev_requirement' => true,
),
'phar-io/version' => array(
'pretty_version' => '3.2.1',
'version' => '3.2.1.0',
'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74',
'type' => 'library',
'install_path' => __DIR__ . '/../phar-io/version',
'aliases' => array(),
'dev_requirement' => true,
),
'php-di/invoker' => array(
'pretty_version' => '2.3.7',
'version' => '2.3.7.0',
@@ -619,6 +745,60 @@
'aliases' => array(),
'dev_requirement' => false,
),
'phpunit/php-code-coverage' => array(
'pretty_version' => '12.5.6',
'version' => '12.5.6.0',
'reference' => '876099a072646c7745f673d7aeab5382c4439691',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-code-coverage',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-file-iterator' => array(
'pretty_version' => '6.0.1',
'version' => '6.0.1.0',
'reference' => '3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-file-iterator',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-invoker' => array(
'pretty_version' => '6.0.0',
'version' => '6.0.0.0',
'reference' => '12b54e689b07a25a9b41e57736dfab6ec9ae5406',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-invoker',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-text-template' => array(
'pretty_version' => '5.0.0',
'version' => '5.0.0.0',
'reference' => 'e1367a453f0eda562eedb4f659e13aa900d66c53',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-text-template',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-timer' => array(
'pretty_version' => '8.0.0',
'version' => '8.0.0.0',
'reference' => 'f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-timer',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/phpunit' => array(
'pretty_version' => '12.5.24',
'version' => '12.5.24.0',
'reference' => 'd75dd30597caa80e72fad2ef7904601a30ef1046',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/phpunit',
'aliases' => array(),
'dev_requirement' => true,
),
'psr/cache' => array(
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
@@ -847,6 +1027,123 @@
'aliases' => array(),
'dev_requirement' => false,
),
'sebastian/cli-parser' => array(
'pretty_version' => '4.2.0',
'version' => '4.2.0.0',
'reference' => '90f41072d220e5c40df6e8635f5dafba2d9d4d04',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/cli-parser',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/comparator' => array(
'pretty_version' => '7.1.6',
'version' => '7.1.6.0',
'reference' => 'c769009dee98f494e0edc3fd4f4087501688f11e',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/comparator',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/complexity' => array(
'pretty_version' => '5.0.0',
'version' => '5.0.0.0',
'reference' => 'bad4316aba5303d0221f43f8cee37eb58d384bbb',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/complexity',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/diff' => array(
'pretty_version' => '7.0.0',
'version' => '7.0.0.0',
'reference' => '7ab1ea946c012266ca32390913653d844ecd085f',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/diff',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/environment' => array(
'pretty_version' => '8.1.0',
'version' => '8.1.0.0',
'reference' => 'b121608b28a13f721e76ffbbd386d08eff58f3f6',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/environment',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/exporter' => array(
'pretty_version' => '7.0.2',
'version' => '7.0.2.0',
'reference' => '016951ae10980765e4e7aee491eb288c64e505b7',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/exporter',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/global-state' => array(
'pretty_version' => '8.0.2',
'version' => '8.0.2.0',
'reference' => 'ef1377171613d09edd25b7816f05be8313f9115d',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/global-state',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/lines-of-code' => array(
'pretty_version' => '4.0.0',
'version' => '4.0.0.0',
'reference' => '97ffee3bcfb5805568d6af7f0f893678fc076d2f',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/lines-of-code',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/object-enumerator' => array(
'pretty_version' => '7.0.0',
'version' => '7.0.0.0',
'reference' => '1effe8e9b8e068e9ae228e542d5d11b5d16db894',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/object-enumerator',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/object-reflector' => array(
'pretty_version' => '5.0.0',
'version' => '5.0.0.0',
'reference' => '4bfa827c969c98be1e527abd576533293c634f6a',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/object-reflector',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/recursion-context' => array(
'pretty_version' => '7.0.1',
'version' => '7.0.1.0',
'reference' => '0b01998a7d5b1f122911a66bebcb8d46f0c82d8c',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/recursion-context',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/type' => array(
'pretty_version' => '6.0.3',
'version' => '6.0.3.0',
'reference' => 'e549163b9760b8f71f191651d22acf32d56d6d4d',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/type',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/version' => array(
'pretty_version' => '6.0.0',
'version' => '6.0.0.0',
'reference' => '3e6ccf7657d4f0a59200564b08cead899313b53c',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/version',
'aliases' => array(),
'dev_requirement' => true,
),
'spatie/laravel-package-tools' => array(
'pretty_version' => '1.93.0',
'version' => '1.93.0.0',
@@ -871,6 +1168,15 @@
0 => '*',
),
),
'staabm/side-effects-detector' => array(
'pretty_version' => '1.0.5',
'version' => '1.0.5.0',
'reference' => 'd8334211a140ce329c13726d4a715adbddd0a163',
'type' => 'library',
'install_path' => __DIR__ . '/../staabm/side-effects-detector',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/clock' => array(
'pretty_version' => 'v8.0.8',
'version' => '8.0.8.0',
@@ -1162,6 +1468,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'theseer/tokenizer' => array(
'pretty_version' => '2.0.1',
'version' => '2.0.1.0',
'reference' => '7989e43bf381af0eac72e4f0ca5bcbfa81658be4',
'type' => 'library',
'install_path' => __DIR__ . '/../theseer/tokenizer',
'aliases' => array(),
'dev_requirement' => true,
),
'tijsverkoyen/css-to-inline-styles' => array(
'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0',
+209
View File
@@ -0,0 +1,209 @@
# CHANGELOG
## [Unreleased](https://github.com/FakerPHP/Faker/compare/v1.24.0...1.24.1)
- Removed domain `gmail.com.au` from `Provider\en_AU\Internet` (#886)
## [2024-11-09, v1.24.0](https://github.com/FakerPHP/Faker/compare/v1.23.1..v1.24.0)
- Fix internal deprecations in Doctrine's populator by @gnutix in (#889)
- Fix mobile phone number pattern for France by @ker0x in (#859)
- PHP 8.4 Support by @Jubeki in (#904)
- Added support for PHP 8.4 (#904)
## [2023-09-29, v1.23.1](https://github.com/FakerPHP/Faker/compare/v1.23.0..v1.23.1)
- Fixed double `а` female lastName in `ru_RU/Person::name()` (#832)
- Fixed polish license plates (#685)
- Stopped using `static` in callables in `Provider\pt_BR\PhoneNumber` (#785)
- Fixed incorrect female name (#794)
- Stopped using the deprecated `MT_RAND_PHP` constant to seed the random generator on PHP 8.3 (#844)
## [2023-06-12, v1.23.0](https://github.com/FakerPHP/Faker/compare/v1.22.0..v1.23.0)
- Update `randomElements` to return random number of elements when no count is provided (#658)
## [2023-05-14, v1.22.0](https://github.com/FakerPHP/Faker/compare/v1.21.0..v1.22.0)
- Fixed `randomElements()` to accept empty iterator (#605)
- Added support for passing an `Enum` to `randomElement()` and `randomElements()` (#620)
- Started rejecting invalid arguments passed to `randomElement()` and `randomElements()` (#642)
## [2022-12-13, v1.21.0](https://github.com/FakerPHP/Faker/compare/v1.20.0..v1.21.0)
- Dropped support for PHP 7.1, 7.2, and 7.3 (#543)
- Added support for PHP 8.2 (#528)
## [2022-07-20, v1.20.0](https://github.com/FakerPHP/Faker/compare/v1.19.0..v1.20.0)
- Fixed typo in French phone number (#452)
- Fixed some Hungarian naming bugs (#451)
- Fixed bug where the NL-BE VAT generation was incorrect (#455)
- Improve Turkish phone numbers for E164 and added landline support (#460)
- Add Microsoft Edge User Agent (#464)
- Added option to set image formats on Faker\Provider\Image (#473)
- Added support for French color translations (#466)
- Support filtering timezones by country code (#480)
- Fixed typo in some greek names (#490)
- Marked the Faker\Provider\Image as deprecated
## [2022-02-02, v1.19.0](https://github.com/FakerPHP/Faker/compare/v1.18.0..v1.19.0)
- Added color extension to core (#442)
- Added conflict with `doctrine/persistence` below version `1.4`
- Fix for support on different Doctrine ORM versions (#414)
- Fix usage of `Doctrine\Persistence` dependency
- Fix CZ Person birthNumber docblock return type (#437)
- Fix is_IS Person docbock types (#439)
- Fix is_IS Address docbock type (#438)
- Fix regexify escape backslash in character class (#434)
- Removed UUID from Generator to be able to extend it (#441)
## [2022-01-23, v1.18.0](https://github.com/FakerPHP/Faker/compare/v1.17.0..v1.18.0)
- Deprecated UUID, use uuid3 to specify version (#427)
- Reset formatters when adding a new provider (#366)
- Helper methods to use our custom generators (#155)
- Set allow-plugins for Composer 2.2 (#405)
- Fix kk_KZ\Person::individualIdentificationNumber generation (#411)
- Allow for -> syntax to be used in parsing (#423)
- Person->name was missing string return type (#424)
- Generate a valid BE TAX number (#415)
- Added the UUID extension to Core (#427)
## [2021-12-05, v1.17.0](https://github.com/FakerPHP/Faker/compare/v1.16.0..v1.17.0)
- Partial PHP 8.1 compatibility (#373)
- Add payment provider for `ne_NP` locale (#375)
- Add Egyptian Arabic `ar_EG` locale (#377)
- Updated list of South African TLDs (#383)
- Fixed formatting of E.164 numbers (#380)
- Allow `symfony/deprecation-contracts` `^3.0` (#397)
## [2021-09-06, v1.16.0](https://github.com/FakerPHP/Faker/compare/v1.15.0..v1.16.0)
- Add Company extension
- Add Address extension
- Add Person extension
- Add PhoneNumber extension
- Add VersionExtension (#350)
- Stricter types in Extension\Container and Extension\GeneratorAwareExtension (#345)
- Fix deprecated property access in `nl_NL` (#348)
- Add support for `psr/container` >= 2.0 (#354)
- Add missing union types in Faker\Generator (#352)
## [2021-07-06, v1.15.0](https://github.com/FakerPHP/Faker/compare/v1.14.1..v1.15.0)
- Updated the generator phpdoc to help identify magic methods (#307)
- Prevent direct access and triggered deprecation warning for "word" (#302)
- Updated length on all global e164 numbers (#301)
- Updated last names from different source (#312)
- Don't generate birth number of '000' for Swedish personal identity (#306)
- Add job list for localization id_ID (#339)
## [2021-03-30, v1.14.1](https://github.com/FakerPHP/Faker/compare/v1.14.0..v1.14.1)
- Fix where randomNumber and randomFloat would return a 0 value (#291 / #292)
## [2021-03-29, v1.14.0](https://github.com/FakerPHP/Faker/compare/v1.13.0..v1.14.0)
- Fix for realText to ensure the text keeps closer to its boundaries (#152)
- Fix where regexify produces a random character instead of a literal dot (#135
- Deprecate zh_TW methods that only call base methods (#122)
- Add used extensions to composer.json as suggestion (#120)
- Moved TCNo and INN from calculator to localized providers (#108)
- Fix regex dot/backslash issue where a dot is replaced with a backslash as escape character (#206)
- Deprecate direct property access (#164)
- Added test to assert unique() behaviour (#233)
- Added RUC for the es_PE locale (#244)
- Test IBAN formats for Latin America (AR/PE/VE) (#260)
- Added VAT number for en_GB (#255)
- Added new districts for the ne_NP locale (#258)
- Fix for U.S. Area Code Generation (#261)
- Fix in numerify where a better random numeric value is guaranteed (#256)
- Fix e164PhoneNumber to only generate valid phone numbers with valid country codes (#264)
- Extract fixtures into separate classes (#234)
- Remove french domains that no longer exists (#277)
- Fix error that occurs when getting a polish title (#279)
- Use valid area codes for North America E164 phone numbers (#280)
- Adding support for extensions and PSR-11 (#154)
- Adding trait for GeneratorAwareExtension (#165)
- Added helper class for extension (#162)
- Added blood extension to core (#232)
- Added barcode extension to core (#252)
- Added number extension (#257)
- Various code style updates
- Added a note about our breaking change promise (#273)
## [2020-12-18, v1.13.0](https://github.com/FakerPHP/Faker/compare/v1.12.1..v1.13.0)
Several fixes and new additions in this release. A lot of cleanup has been done
on the codebase on both tests and consistency.
- Feature/pl pl license plate (#62)
- Fix greek phone numbers (#16)
- Move AT payment provider logic to de_AT (#72)
- Fix wiktionary links (#73)
- Fix AT person links (#74)
- Fix AT cities (#75)
- Deprecate at_AT providers (#78)
- Add Austrian `ssn()` to `Person` provider (#79)
- Fix typos in id_ID Address (#83)
- Austrian post codes (#86)
- Updated Polish data (#70)
- Improve Austrian social security number generation (#88)
- Move US phone numbers with extension to own method (#91)
- Add UK National Insurance number generator (#89)
- Fix en_SG phone number generator (#100)
- Remove usage of mt_rand (#87)
- Remove whitespace from beginning of el_GR phone numbers (#105)
- Building numbers can not be 0, 00, 000 (#107)
- Add 172.16/12 local IPv4 block (#121)
- Add JCB credit card type (#124)
- Remove json_decode from emoji generation (#123)
- Remove ro street address (#146)
## [2020-12-11, v1.12.1](https://github.com/FakerPHP/Faker/compare/v1.12.0..v1.12.1)
This is a security release that prevents a hacker to execute code on the server.
## [2020-11-23, v1.12.0](https://github.com/FakerPHP/Faker/compare/v1.11.0..v1.12.0)
- Fix ro_RO first and last day of year calculation offset (#65)
- Fix en_NG locale test namespaces that did not match PSR-4 (#57)
- Added Singapore NRIC/FIN provider (#56)
- Added provider for Lithuanian municipalities (#58)
- Added blood types provider (#61)
## [2020-11-15, v1.11.0](https://github.com/FakerPHP/Faker/compare/v1.10.1..v1.11.0)
- Added Provider for Swedish Municipalities
- Updates to person names in pt_BR
- Many code style changes
## [2020-10-28, v1.10.1](https://github.com/FakerPHP/Faker/compare/v1.10.0..v1.10.1)
- Updates the Danish addresses in dk_DK
- Removed offense company names in nl_NL
- Clarify changelog with original fork
- Standin replacement for LoremPixel to Placeholder.com (#11)
## [2020-10-27, v1.10.0](https://github.com/FakerPHP/Faker/compare/v1.9.1..v1.10.0)
- Support PHP 7.1-8.0
- Fix typo in de_DE Company Provider
- Fix dateTimeThisYear method
- Fix typo in de_DE jobTitleFormat
- Fix IBAN generation for CR
- Fix typos in greek first names
- Fix US job title typo
- Do not clear entity manager for doctrine orm populator
- Remove persian rude words
- Corrections to RU names
## 2020-10-27, v1.9.1
- Initial version. Same as `fzaninotto/Faker:v1.9.1`.
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2011 François Zaninotto
Portions Copyright (c) 2008 Caius Durling
Portions Copyright (c) 2008 Adam Royle
Portions Copyright (c) 2008 Fiona Burrows
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+114
View File
@@ -0,0 +1,114 @@
<p style="text-align: center"><img src="https://github.com/FakerPHP/Artwork/raw/main/src/socialcard.png" alt="Social card of FakerPHP"></p>
# Faker
[![Packagist Downloads](https://img.shields.io/packagist/dm/FakerPHP/Faker)](https://packagist.org/packages/fakerphp/faker)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/FakerPHP/Faker/Tests/main)](https://github.com/FakerPHP/Faker/actions)
[![Type Coverage](https://shepherd.dev/github/FakerPHP/Faker/coverage.svg)](https://shepherd.dev/github/FakerPHP/Faker)
[![Code Coverage](https://codecov.io/gh/FakerPHP/Faker/branch/main/graph/badge.svg)](https://codecov.io/gh/FakerPHP/Faker)
Faker is a PHP library that generates fake data for you. Whether you need to bootstrap your database, create good-looking XML documents, fill-in your persistence to stress test it, or anonymize data taken from a production service, Faker is for you.
It's heavily inspired by Perl's [Data::Faker](https://metacpan.org/pod/Data::Faker), and by Ruby's [Faker](https://rubygems.org/gems/faker).
## Getting Started
### Installation
Faker requires PHP >= 7.4.
```shell
composer require fakerphp/faker
```
### Documentation
Full documentation can be found over on [fakerphp.github.io](https://fakerphp.github.io).
### Basic Usage
Use `Faker\Factory::create()` to create and initialize a Faker generator, which can generate data by accessing methods named after the type of data you want.
```php
<?php
require_once 'vendor/autoload.php';
// use the factory to create a Faker\Generator instance
$faker = Faker\Factory::create();
// generate data by calling methods
echo $faker->name();
// 'Vince Sporer'
echo $faker->email();
// 'walter.sophia@hotmail.com'
echo $faker->text();
// 'Numquam ut mollitia at consequuntur inventore dolorem.'
```
Each call to `$faker->name()` yields a different (random) result. This is because Faker uses `__call()` magic, and forwards `Faker\Generator->$method()` calls to `Faker\Generator->format($method, $attributes)`.
```php
<?php
for ($i = 0; $i < 3; $i++) {
echo $faker->name() . "\n";
}
// 'Cyrus Boyle'
// 'Alena Cummerata'
// 'Orlo Bergstrom'
```
## Automated refactoring
If you already used this library with its properties, they are now deprecated and needs to be replaced by their equivalent methods.
You can use the provided [Rector](https://github.com/rectorphp/rector) config file to automate the work.
Run
```bash
composer require --dev rector/rector
```
to install `rector/rector`.
Run
```bash
vendor/bin/rector process src/ --config vendor/fakerphp/faker/rector-migrate.php
```
to run `rector/rector`.
*Note:* do not forget to replace `src/` with the path to your source directory.
Alternatively, import the configuration in your `rector.php` file:
```php
<?php
declare(strict_types=1);
use Rector\Config;
return static function (Config\RectorConfig $rectorConfig): void {
$rectorConfig->import('vendor/fakerphp/faker/rector-migrate.php');
};
```
## License
Faker is released under the MIT License. See [`LICENSE`](LICENSE) for details.
## Backward compatibility promise
Faker is using [Semver](https://semver.org/). This means that versions are tagged
with MAJOR.MINOR.PATCH. Only a new major version will be allowed to break backward
compatibility (BC).
Classes marked as `@experimental` or `@internal` are not included in our backward compatibility promise.
You are also not guaranteed that the value returned from a method is always the
same. You are guaranteed that the data type will not change.
PHP 8 introduced [named arguments](https://wiki.php.net/rfc/named_params), which
increased the cost and reduces flexibility for package maintainers. The names of the
arguments for methods in Faker is not included in our BC promise.
+56
View File
@@ -0,0 +1,56 @@
{
"name": "fakerphp/faker",
"type": "library",
"description": "Faker is a PHP library that generates fake data for you.",
"keywords": [
"faker",
"fixtures",
"data"
],
"license": "MIT",
"authors": [
{
"name": "François Zaninotto"
}
],
"require": {
"php": "^7.4 || ^8.0",
"psr/container": "^1.0 || ^2.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"require-dev": {
"ext-intl": "*",
"bamarni/composer-bin-plugin": "^1.4.1",
"doctrine/persistence": "^1.3 || ^2.0",
"phpunit/phpunit": "^9.5.26",
"symfony/phpunit-bridge": "^5.4.16"
},
"autoload": {
"psr-4": {
"Faker\\": "src/Faker/"
}
},
"autoload-dev": {
"psr-4": {
"Faker\\Test\\": "test/Faker/",
"Faker\\Test\\Fixture\\": "test/Fixture/"
}
},
"conflict": {
"fzaninotto/faker": "*"
},
"suggest": {
"ext-curl": "Required by Faker\\Provider\\Image to download images.",
"ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.",
"ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.",
"ext-mbstring": "Required for multibyte Unicode string functionality.",
"doctrine/orm": "Required to use Faker\\ORM\\Doctrine"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true,
"composer/package-versions-deprecated": true
},
"sort-packages": true
}
}
+161
View File
@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
use Faker\Generator;
use Rector\Config;
use Rector\Transform;
// This file configures rector/rector to replace all deprecated property usages with their equivalent functions.
return static function (Config\RectorConfig $rectorConfig): void {
$properties = [
'address',
'amPm',
'asciify',
'biasedNumberBetween',
'boolean',
'bothify',
'buildingNumber',
'century',
'chrome',
'city',
'citySuffix',
'colorName',
'company',
'companyEmail',
'companySuffix',
'country',
'countryCode',
'countryISOAlpha3',
'creditCardDetails',
'creditCardExpirationDate',
'creditCardExpirationDateString',
'creditCardNumber',
'creditCardType',
'currencyCode',
'date',
'dateTime',
'dateTimeAD',
'dateTimeBetween',
'dateTimeInInterval',
'dateTimeThisCentury',
'dateTimeThisDecade',
'dateTimeThisMonth',
'dateTimeThisYear',
'dayOfMonth',
'dayOfWeek',
'domainName',
'domainWord',
'e164PhoneNumber',
'email',
'emoji',
'file',
'firefox',
'firstName',
'firstNameFemale',
'firstNameMale',
'freeEmail',
'freeEmailDomain',
'getDefaultTimezone',
'hexColor',
'hslColor',
'hslColorAsArray',
'iban',
'image',
'imageUrl',
'imei',
'internetExplorer',
'iosMobileToken',
'ipv4',
'ipv6',
'iso8601',
'jobTitle',
'languageCode',
'lastName',
'latitude',
'lexify',
'linuxPlatformToken',
'linuxProcessor',
'localCoordinates',
'localIpv4',
'locale',
'longitude',
'macAddress',
'macPlatformToken',
'macProcessor',
'md5',
'month',
'monthName',
'msedge',
'name',
'numerify',
'opera',
'paragraph',
'paragraphs',
'passthrough',
'password',
'phoneNumber',
'postcode',
'randomAscii',
'randomDigitNotNull',
'randomElement',
'randomElements',
'randomHtml',
'randomKey',
'randomLetter',
'realText',
'realTextBetween',
'regexify',
'rgbColor',
'rgbColorAsArray',
'rgbCssColor',
'rgbaCssColor',
'safari',
'safeColorName',
'safeEmail',
'safeEmailDomain',
'safeHexColor',
'sentence',
'sentences',
'setDefaultTimezone',
'sha1',
'sha256',
'shuffle',
'shuffleArray',
'shuffleString',
'slug',
'streetAddress',
'streetName',
'streetSuffix',
'swiftBicNumber',
'text',
'time',
'timezone',
'title',
'titleFemale',
'titleMale',
'tld',
'toLower',
'toUpper',
'unixTime',
'url',
'userAgent',
'userName',
'uuid',
'windowsPlatformToken',
'word',
'words',
'year',
];
$rectorConfig->ruleWithConfiguration(
Transform\Rector\Assign\PropertyFetchToMethodCallRector::class,
array_map(static function (string $property): Transform\ValueObject\PropertyFetchToMethodCall {
return new Transform\ValueObject\PropertyFetchToMethodCall(
Generator::class,
$property,
$property,
);
}, $properties),
);
};
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Faker\Calculator;
/**
* Utility class for validating EAN-8 and EAN-13 numbers
*/
class Ean
{
/**
* @var string EAN validation pattern
*/
public const PATTERN = '/^(?:\d{8}|\d{13})$/';
/**
* Computes the checksum of an EAN number.
*
* @see https://en.wikipedia.org/wiki/International_Article_Number
*
* @return int
*/
public static function checksum(string $digits)
{
$sequence = (strlen($digits) + 1) === 8 ? [3, 1] : [1, 3];
$sums = 0;
foreach (str_split($digits) as $n => $digit) {
$sums += ((int) $digit) * $sequence[$n % 2];
}
return (10 - $sums % 10) % 10;
}
/**
* Checks whether the provided number is an EAN compliant number and that
* the checksum is correct.
*
* @param string $ean An EAN number
*
* @return bool
*/
public static function isValid(string $ean)
{
if (!preg_match(self::PATTERN, $ean)) {
return false;
}
return self::checksum(substr($ean, 0, -1)) === (int) substr($ean, -1);
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace Faker\Calculator;
class Iban
{
/**
* Generates IBAN Checksum
*
* @return string Checksum (numeric string)
*/
public static function checksum(string $iban)
{
// Move first four digits to end and set checksum to '00'
$checkString = substr($iban, 4) . substr($iban, 0, 2) . '00';
// Replace all letters with their number equivalents
$checkString = preg_replace_callback(
'/[A-Z]/',
static function (array $matches): string {
return (string) self::alphaToNumber($matches[0]);
},
$checkString,
);
// Perform mod 97 and subtract from 98
$checksum = 98 - self::mod97($checkString);
return str_pad($checksum, 2, '0', STR_PAD_LEFT);
}
/**
* Converts letter to number
*
* @return int
*/
public static function alphaToNumber(string $char)
{
return ord($char) - 55;
}
/**
* Calculates mod97 on a numeric string
*
* @param string $number Numeric string
*
* @return int
*/
public static function mod97(string $number)
{
$checksum = (int) $number[0];
for ($i = 1, $size = strlen($number); $i < $size; ++$i) {
$checksum = (10 * $checksum + (int) $number[$i]) % 97;
}
return $checksum;
}
/**
* Checks whether an IBAN has a valid checksum
*
* @return bool
*/
public static function isValid(string $iban)
{
return self::checksum($iban) === substr($iban, 2, 2);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace Faker\Calculator;
/**
* @deprecated moved to ru_RU\Company, use {@link \Faker\Provider\ru_RU\Company}.
* @see \Faker\Provider\ru_RU\Company
*/
class Inn
{
/**
* Generates INN Checksum
*
* https://ru.wikipedia.org/wiki/%D0%98%D0%B4%D0%B5%D0%BD%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%BE%D0%BC%D0%B5%D1%80_%D0%BD%D0%B0%D0%BB%D0%BE%D0%B3%D0%BE%D0%BF%D0%BB%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%89%D0%B8%D0%BA%D0%B0
*
* @param string $inn
*
* @return string Checksum (one digit)
*
* @deprecated use {@link \Faker\Provider\ru_RU\Company::inn10Checksum()} instead
* @see \Faker\Provider\ru_RU\Company::inn10Checksum()
*/
public static function checksum($inn)
{
return \Faker\Provider\ru_RU\Company::inn10Checksum($inn);
}
/**
* Checks whether an INN has a valid checksum
*
* @param string $inn
*
* @return bool
*
* @deprecated use {@link \Faker\Provider\ru_RU\Company::inn10IsValid()} instead
* @see \Faker\Provider\ru_RU\Company::inn10IsValid()
*/
public static function isValid($inn)
{
return \Faker\Provider\ru_RU\Company::inn10IsValid($inn);
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace Faker\Calculator;
/**
* Utility class for validating ISBN-10
*/
class Isbn
{
/**
* @var string ISBN-10 validation pattern
*/
public const PATTERN = '/^\d{9}[0-9X]$/';
/**
* ISBN-10 check digit
*
* @see http://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-10_check_digits
*
* @param string $input ISBN without check-digit
*
* @throws \LengthException When wrong input length passed
*/
public static function checksum(string $input): string
{
// We're calculating check digit for ISBN-10
// so, the length of the input should be 9
$length = 9;
if (strlen($input) !== $length) {
throw new \LengthException(sprintf('Input length should be equal to %d', $length));
}
$digits = str_split($input);
array_walk(
$digits,
static function (&$digit, $position): void {
$digit = (10 - $position) * $digit;
},
);
$result = (11 - array_sum($digits) % 11) % 11;
// 10 is replaced by X
return ($result < 10) ? (string) $result : 'X';
}
/**
* Checks whether the provided number is a valid ISBN-10 number
*
* @param string $isbn ISBN to check
*/
public static function isValid(string $isbn): bool
{
if (!preg_match(self::PATTERN, $isbn)) {
return false;
}
return self::checksum(substr($isbn, 0, -1)) === substr($isbn, -1);
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace Faker\Calculator;
/**
* Utility class for generating and validating Luhn numbers.
*
* Luhn algorithm is used to validate credit card numbers, IMEI numbers, and
* National Provider Identifier numbers.
*
* @see http://en.wikipedia.org/wiki/Luhn_algorithm
*/
class Luhn
{
/**
* @return int
*/
private static function checksum(string $number)
{
$number = (string) $number;
$length = strlen($number);
$sum = 0;
for ($i = $length - 1; $i >= 0; $i -= 2) {
$sum += $number[$i];
}
for ($i = $length - 2; $i >= 0; $i -= 2) {
$sum += array_sum(str_split($number[$i] * 2));
}
return $sum % 10;
}
/**
* @return string
*/
public static function computeCheckDigit(string $partialNumber)
{
$checkDigit = self::checksum($partialNumber . '0');
if ($checkDigit === 0) {
return '0';
}
return (string) (10 - $checkDigit);
}
/**
* Checks whether a number (partial number + check digit) is Luhn compliant
*
* @return bool
*/
public static function isValid(string $number)
{
return self::checksum($number) === 0;
}
/**
* Generate a Luhn compliant number.
*
* @return string
*/
public static function generateLuhnNumber(string $partialValue)
{
if (!preg_match('/^\d+$/', $partialValue)) {
throw new \InvalidArgumentException('Argument should be an integer.');
}
return $partialValue . Luhn::computeCheckDigit($partialValue);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace Faker\Calculator;
/**
* @deprecated moved to tr_TR\Person, use {@link \Faker\Provider\tr_TR\Person}.
* @see \Faker\Provider\tr_TR\Person
*/
class TCNo
{
/**
* Generates Turkish Identity Number Checksum
* Gets first 9 digit as prefix and calculates checksum
*
* https://en.wikipedia.org/wiki/Turkish_Identification_Number
*
* @param string $identityPrefix
*
* @return string Checksum (two digit)
*
* @deprecated use {@link \Faker\Provider\tr_TR\Person::tcNoChecksum()} instead
* @see \Faker\Provider\tr_TR\Person::tcNoChecksum()
*/
public static function checksum($identityPrefix)
{
return \Faker\Provider\tr_TR\Person::tcNoChecksum($identityPrefix);
}
/**
* Checks whether a TCNo has a valid checksum
*
* @param string $tcNo
*
* @return bool
*
* @deprecated use {@link \Faker\Provider\tr_TR\Person::tcNoIsValid()} instead
* @see \Faker\Provider\tr_TR\Person::tcNoIsValid()
*/
public static function isValid($tcNo)
{
return \Faker\Provider\tr_TR\Person::tcNoIsValid($tcNo);
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace Faker;
use Faker\Extension\Extension;
/**
* This generator returns a default value for all called properties
* and methods. It works with Faker\Generator::optional().
*
* @mixin Generator
*/
class ChanceGenerator
{
private $generator;
private $weight;
protected $default;
/**
* @param Extension|Generator $generator
*/
public function __construct($generator, float $weight, $default = null)
{
$this->default = $default;
$this->generator = $generator;
$this->weight = $weight;
}
public function ext(string $id)
{
return new self($this->generator->ext($id), $this->weight, $this->default);
}
/**
* Catch and proxy all generator calls but return only valid values
*
* @param string $attribute
*
* @deprecated Use a method instead.
*/
public function __get($attribute)
{
trigger_deprecation('fakerphp/faker', '1.14', 'Accessing property "%s" is deprecated, use "%s()" instead.', $attribute, $attribute);
return $this->__call($attribute, []);
}
/**
* @param string $name
* @param array $arguments
*/
public function __call($name, $arguments)
{
if (mt_rand(1, 100) <= (100 * $this->weight)) {
return call_user_func_array([$this->generator, $name], $arguments);
}
return $this->default;
}
}
+139
View File
@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace Faker\Container;
use Faker\Extension\Extension;
/**
* A simple implementation of a container.
*
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Container implements ContainerInterface
{
/**
* @var array<string, callable|object|string>
*/
private array $definitions;
private array $services = [];
/**
* Create a container object with a set of definitions. The array value MUST
* produce an object that implements Extension.
*
* @param array<string, callable|object|string> $definitions
*/
public function __construct(array $definitions)
{
$this->definitions = $definitions;
}
/**
* Retrieve a definition from the container.
*
* @param string $id
*
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws ContainerException
* @throws NotInContainerException
*/
public function get($id): Extension
{
if (!is_string($id)) {
throw new \InvalidArgumentException(sprintf(
'First argument of %s::get() must be string',
self::class,
));
}
if (array_key_exists($id, $this->services)) {
return $this->services[$id];
}
if (!$this->has($id)) {
throw new NotInContainerException(sprintf(
'There is not service with id "%s" in the container.',
$id,
));
}
$definition = $this->definitions[$id];
$service = $this->getService($id, $definition);
if (!$service instanceof Extension) {
throw new \RuntimeException(sprintf(
'Service resolved for identifier "%s" does not implement the %s" interface.',
$id,
Extension::class,
));
}
$this->services[$id] = $service;
return $service;
}
/**
* Get the service from a definition.
*
* @param callable|object|string $definition
*/
private function getService(string $id, $definition)
{
if (is_callable($definition)) {
try {
return $definition();
} catch (\Throwable $e) {
throw new ContainerException(
sprintf('Error while invoking callable for "%s"', $id),
0,
$e,
);
}
} elseif (is_object($definition)) {
return $definition;
} elseif (is_string($definition)) {
if (class_exists($definition)) {
try {
return new $definition();
} catch (\Throwable $e) {
throw new ContainerException(sprintf('Could not instantiate class "%s"', $id), 0, $e);
}
}
throw new ContainerException(sprintf(
'Could not instantiate class "%s". Class was not found.',
$id,
));
} else {
throw new ContainerException(sprintf(
'Invalid type for definition with id "%s"',
$id,
));
}
}
/**
* Check if the container contains a given identifier.
*
* @param string $id
*
* @throws \InvalidArgumentException
*/
public function has($id): bool
{
if (!is_string($id)) {
throw new \InvalidArgumentException(sprintf(
'First argument of %s::get() must be string',
self::class,
));
}
return array_key_exists($id, $this->definitions);
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Faker\Container;
use Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class ContainerBuilder
{
/**
* @var array<string, callable|object|string>
*/
private array $definitions = [];
/**
* @param callable|object|string $definition
*
* @throws \InvalidArgumentException
*/
public function add(string $id, $definition): self
{
if (!is_string($definition) && !is_callable($definition) && !is_object($definition)) {
throw new \InvalidArgumentException(sprintf(
'First argument to "%s::add()" must be a string, callable or object.',
self::class,
));
}
$this->definitions[$id] = $definition;
return $this;
}
public function build(): ContainerInterface
{
return new Container($this->definitions);
}
private static function defaultExtensions(): array
{
return [
Extension\BarcodeExtension::class => Core\Barcode::class,
Extension\BloodExtension::class => Core\Blood::class,
Extension\ColorExtension::class => Core\Color::class,
Extension\DateTimeExtension::class => Core\DateTime::class,
Extension\FileExtension::class => Core\File::class,
Extension\NumberExtension::class => Core\Number::class,
Extension\UuidExtension::class => Core\Uuid::class,
Extension\VersionExtension::class => Core\Version::class,
];
}
public static function withDefaultExtensions(): self
{
$instance = new self();
foreach (self::defaultExtensions() as $id => $definition) {
$instance->add($id, $definition);
}
return $instance;
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Faker\Container;
use Psr\Container\ContainerExceptionInterface;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class ContainerException extends \RuntimeException implements ContainerExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Faker\Container;
use Psr\Container\ContainerInterface as BaseContainerInterface;
interface ContainerInterface extends BaseContainerInterface
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Faker\Container;
use Psr\Container\NotFoundExceptionInterface;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class NotInContainerException extends \RuntimeException implements NotFoundExceptionInterface
{
}
+52
View File
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Calculator;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Barcode implements Extension\BarcodeExtension
{
private Extension\NumberExtension $numberExtension;
public function __construct(?Extension\NumberExtension $numberExtension = null)
{
$this->numberExtension = $numberExtension ?: new Number();
}
private function ean(int $length = 13): string
{
$code = Extension\Helper::numerify(str_repeat('#', $length - 1));
return sprintf('%s%s', $code, Calculator\Ean::checksum($code));
}
public function ean13(): string
{
return $this->ean();
}
public function ean8(): string
{
return $this->ean(8);
}
public function isbn10(): string
{
$code = Extension\Helper::numerify(str_repeat('#', 9));
return sprintf('%s%s', $code, Calculator\Isbn::checksum($code));
}
public function isbn13(): string
{
$code = '97' . $this->numberExtension->numberBetween(8, 9) . Extension\Helper::numerify(str_repeat('#', 9));
return sprintf('%s%s', $code, Calculator\Ean::checksum($code));
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Blood implements Extension\BloodExtension
{
/**
* @var string[]
*/
private array $bloodTypes = ['A', 'AB', 'B', 'O'];
/**
* @var string[]
*/
private array $bloodRhFactors = ['+', '-'];
public function bloodType(): string
{
return Extension\Helper::randomElement($this->bloodTypes);
}
public function bloodRh(): string
{
return Extension\Helper::randomElement($this->bloodRhFactors);
}
public function bloodGroup(): string
{
return sprintf(
'%s%s',
$this->bloodType(),
$this->bloodRh(),
);
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
use Faker\Extension\Helper;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Color implements Extension\ColorExtension
{
private Extension\NumberExtension $numberExtension;
/**
* @var string[]
*/
private array $safeColorNames = [
'black', 'maroon', 'green', 'navy', 'olive',
'purple', 'teal', 'lime', 'blue', 'silver',
'gray', 'yellow', 'fuchsia', 'aqua', 'white',
];
/**
* @var string[]
*/
private array $allColorNames = [
'AliceBlue', 'AntiqueWhite', 'Aqua', 'Aquamarine',
'Azure', 'Beige', 'Bisque', 'Black', 'BlanchedAlmond',
'Blue', 'BlueViolet', 'Brown', 'BurlyWood', 'CadetBlue',
'Chartreuse', 'Chocolate', 'Coral', 'CornflowerBlue',
'Cornsilk', 'Crimson', 'Cyan', 'DarkBlue', 'DarkCyan',
'DarkGoldenRod', 'DarkGray', 'DarkGreen', 'DarkKhaki',
'DarkMagenta', 'DarkOliveGreen', 'Darkorange', 'DarkOrchid',
'DarkRed', 'DarkSalmon', 'DarkSeaGreen', 'DarkSlateBlue',
'DarkSlateGray', 'DarkTurquoise', 'DarkViolet', 'DeepPink',
'DeepSkyBlue', 'DimGray', 'DimGrey', 'DodgerBlue', 'FireBrick',
'FloralWhite', 'ForestGreen', 'Fuchsia', 'Gainsboro', 'GhostWhite',
'Gold', 'GoldenRod', 'Gray', 'Green', 'GreenYellow', 'HoneyDew',
'HotPink', 'IndianRed', 'Indigo', 'Ivory', 'Khaki', 'Lavender',
'LavenderBlush', 'LawnGreen', 'LemonChiffon', 'LightBlue', 'LightCoral',
'LightCyan', 'LightGoldenRodYellow', 'LightGray', 'LightGreen', 'LightPink',
'LightSalmon', 'LightSeaGreen', 'LightSkyBlue', 'LightSlateGray', 'LightSteelBlue',
'LightYellow', 'Lime', 'LimeGreen', 'Linen', 'Magenta', 'Maroon', 'MediumAquaMarine',
'MediumBlue', 'MediumOrchid', 'MediumPurple', 'MediumSeaGreen', 'MediumSlateBlue',
'MediumSpringGreen', 'MediumTurquoise', 'MediumVioletRed', 'MidnightBlue',
'MintCream', 'MistyRose', 'Moccasin', 'NavajoWhite', 'Navy', 'OldLace', 'Olive',
'OliveDrab', 'Orange', 'OrangeRed', 'Orchid', 'PaleGoldenRod', 'PaleGreen',
'PaleTurquoise', 'PaleVioletRed', 'PapayaWhip', 'PeachPuff', 'Peru', 'Pink', 'Plum',
'PowderBlue', 'Purple', 'Red', 'RosyBrown', 'RoyalBlue', 'SaddleBrown', 'Salmon',
'SandyBrown', 'SeaGreen', 'SeaShell', 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue',
'SlateGray', 'Snow', 'SpringGreen', 'SteelBlue', 'Tan', 'Teal', 'Thistle', 'Tomato',
'Turquoise', 'Violet', 'Wheat', 'White', 'WhiteSmoke', 'Yellow', 'YellowGreen',
];
public function __construct(?Extension\NumberExtension $numberExtension = null)
{
$this->numberExtension = $numberExtension ?: new Number();
}
/**
* @example '#fa3cc2'
*/
public function hexColor(): string
{
return '#' . str_pad(dechex($this->numberExtension->numberBetween(1, 16777215)), 6, '0', STR_PAD_LEFT);
}
/**
* @example '#ff0044'
*/
public function safeHexColor(): string
{
$color = str_pad(dechex($this->numberExtension->numberBetween(0, 255)), 3, '0', STR_PAD_LEFT);
return sprintf(
'#%s%s%s%s%s%s',
$color[0],
$color[0],
$color[1],
$color[1],
$color[2],
$color[2],
);
}
/**
* @example 'array(0,255,122)'
*
* @return int[]
*/
public function rgbColorAsArray(): array
{
$color = $this->hexColor();
return [
hexdec(substr($color, 1, 2)),
hexdec(substr($color, 3, 2)),
hexdec(substr($color, 5, 2)),
];
}
/**
* @example '0,255,122'
*/
public function rgbColor(): string
{
return implode(',', $this->rgbColorAsArray());
}
/**
* @example 'rgb(0,255,122)'
*/
public function rgbCssColor(): string
{
return sprintf(
'rgb(%s)',
$this->rgbColor(),
);
}
/**
* @example 'rgba(0,255,122,0.8)'
*/
public function rgbaCssColor(): string
{
return sprintf(
'rgba(%s,%s)',
$this->rgbColor(),
$this->numberExtension->randomFloat(1, 0, 1),
);
}
/**
* @example 'blue'
*/
public function safeColorName(): string
{
return Helper::randomElement($this->safeColorNames);
}
/**
* @example 'NavajoWhite'
*/
public function colorName(): string
{
return Helper::randomElement($this->allColorNames);
}
/**
* @example '340,50,20'
*/
public function hslColor(): string
{
return sprintf(
'%s,%s,%s',
$this->numberExtension->numberBetween(0, 360),
$this->numberExtension->numberBetween(0, 100),
$this->numberExtension->numberBetween(0, 100),
);
}
/**
* @example array(340, 50, 20)
*
* @return int[]
*/
public function hslColorAsArray(): array
{
return [
$this->numberExtension->numberBetween(0, 360),
$this->numberExtension->numberBetween(0, 100),
$this->numberExtension->numberBetween(0, 100),
];
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Coordinates implements Extension\Extension
{
private Extension\NumberExtension $numberExtension;
public function __construct(?Extension\NumberExtension $numberExtension = null)
{
$this->numberExtension = $numberExtension ?: new Number();
}
/**
* @example '77.147489'
*
* @return float Uses signed degrees format (returns a float number between -90 and 90)
*/
public function latitude(float $min = -90.0, float $max = 90.0): float
{
if ($min < -90 || $max < -90) {
throw new \LogicException('Latitude cannot be less that -90.0');
}
if ($min > 90 || $max > 90) {
throw new \LogicException('Latitude cannot be greater that 90.0');
}
return $this->randomFloat(6, $min, $max);
}
/**
* @example '86.211205'
*
* @return float Uses signed degrees format (returns a float number between -180 and 180)
*/
public function longitude(float $min = -180.0, float $max = 180.0): float
{
if ($min < -180 || $max < -180) {
throw new \LogicException('Longitude cannot be less that -180.0');
}
if ($min > 180 || $max > 180) {
throw new \LogicException('Longitude cannot be greater that 180.0');
}
return $this->randomFloat(6, $min, $max);
}
/**
* @example array('77.147489', '86.211205')
*
* @return array{latitude: float, longitude: float}
*/
public function localCoordinates(): array
{
return [
'latitude' => $this->latitude(),
'longitude' => $this->longitude(),
];
}
private function randomFloat(int $nbMaxDecimals, float $min, float $max): float
{
if ($min > $max) {
throw new \LogicException('Invalid coordinates boundaries');
}
return $this->numberExtension->randomFloat($nbMaxDecimals, $min, $max);
}
}
+217
View File
@@ -0,0 +1,217 @@
<?php
namespace Faker\Core;
use Faker\Extension\DateTimeExtension;
use Faker\Extension\GeneratorAwareExtension;
use Faker\Extension\GeneratorAwareExtensionTrait;
use Faker\Extension\Helper;
/**
* @experimental This class is experimental and does not fall under our BC promise
*
* @since 1.20.0
*/
final class DateTime implements DateTimeExtension, GeneratorAwareExtension
{
use GeneratorAwareExtensionTrait;
/**
* @var string[]
*/
private array $centuries = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV', 'XVI', 'XVII', 'XVIII', 'XIX', 'XX', 'XXI'];
private ?string $defaultTimezone = null;
/**
* Get the POSIX-timestamp of a DateTime, int or string.
*
* @param \DateTime|float|int|string $until
*
* @return false|int
*/
private function getTimestamp($until = 'now')
{
if (is_numeric($until)) {
return (int) $until;
}
if ($until instanceof \DateTime) {
return $until->getTimestamp();
}
return strtotime(empty($until) ? 'now' : $until);
}
/**
* Get a DateTime created based on a POSIX-timestamp.
*
* @param int $timestamp the UNIX / POSIX-compatible timestamp
*/
private function getTimestampDateTime(int $timestamp): \DateTime
{
return new \DateTime('@' . $timestamp);
}
private function resolveTimezone(?string $timezone): string
{
if ($timezone !== null) {
return $timezone;
}
return null === $this->defaultTimezone ? date_default_timezone_get() : $this->defaultTimezone;
}
/**
* Internal method to set the timezone on a DateTime object.
*/
private function setTimezone(\DateTime $dateTime, ?string $timezone): \DateTime
{
$timezone = $this->resolveTimezone($timezone);
return $dateTime->setTimezone(new \DateTimeZone($timezone));
}
public function dateTime($until = 'now', ?string $timezone = null): \DateTime
{
return $this->setTimezone(
$this->getTimestampDateTime($this->unixTime($until)),
$timezone,
);
}
public function dateTimeAD($until = 'now', ?string $timezone = null): \DateTime
{
$min = (PHP_INT_SIZE > 4) ? -62135597361 : -PHP_INT_MAX;
return $this->setTimezone(
$this->getTimestampDateTime($this->generator->numberBetween($min, $this->getTimestamp($until))),
$timezone,
);
}
public function dateTimeBetween($from = '-30 years', $until = 'now', ?string $timezone = null): \DateTime
{
$start = $this->getTimestamp($from);
$end = $this->getTimestamp($until);
if ($start > $end) {
throw new \InvalidArgumentException('"$from" must be anterior to "$until".');
}
$timestamp = $this->generator->numberBetween($start, $end);
return $this->setTimezone(
$this->getTimestampDateTime($timestamp),
$timezone,
);
}
public function dateTimeInInterval($from = '-30 years', string $interval = '+5 days', ?string $timezone = null): \DateTime
{
$intervalObject = \DateInterval::createFromDateString($interval);
$datetime = $from instanceof \DateTime ? $from : new \DateTime($from);
$other = (clone $datetime)->add($intervalObject);
$begin = min($datetime, $other);
$end = $datetime === $begin ? $other : $datetime;
return $this->dateTimeBetween($begin, $end, $timezone);
}
public function dateTimeThisWeek($until = 'sunday this week', ?string $timezone = null): \DateTime
{
return $this->dateTimeBetween('monday this week', $until, $timezone);
}
public function dateTimeThisMonth($until = 'last day of this month', ?string $timezone = null): \DateTime
{
return $this->dateTimeBetween('first day of this month', $until, $timezone);
}
public function dateTimeThisYear($until = 'last day of december', ?string $timezone = null): \DateTime
{
return $this->dateTimeBetween('first day of january', $until, $timezone);
}
public function dateTimeThisDecade($until = 'now', ?string $timezone = null): \DateTime
{
$year = floor(date('Y') / 10) * 10;
return $this->dateTimeBetween("first day of january $year", $until, $timezone);
}
public function dateTimeThisCentury($until = 'now', ?string $timezone = null): \DateTime
{
$year = floor(date('Y') / 100) * 100;
return $this->dateTimeBetween("first day of january $year", $until, $timezone);
}
public function date(string $format = 'Y-m-d', $until = 'now'): string
{
return $this->dateTime($until)->format($format);
}
public function time(string $format = 'H:i:s', $until = 'now'): string
{
return $this->date($format, $until);
}
public function unixTime($until = 'now'): int
{
return $this->generator->numberBetween(0, $this->getTimestamp($until));
}
public function iso8601($until = 'now'): string
{
return $this->date(\DateTime::ISO8601, $until);
}
public function amPm($until = 'now'): string
{
return $this->date('a', $until);
}
public function dayOfMonth($until = 'now'): string
{
return $this->date('d', $until);
}
public function dayOfWeek($until = 'now'): string
{
return $this->date('l', $until);
}
public function month($until = 'now'): string
{
return $this->date('m', $until);
}
public function monthName($until = 'now'): string
{
return $this->date('F', $until);
}
public function year($until = 'now'): string
{
return $this->date('Y', $until);
}
public function century(): string
{
return Helper::randomElement($this->centuries);
}
public function timezone(?string $countryCode = null): string
{
if ($countryCode) {
$timezones = \DateTimeZone::listIdentifiers(\DateTimeZone::PER_COUNTRY, $countryCode);
} else {
$timezones = \DateTimeZone::listIdentifiers();
}
return Helper::randomElement($timezones);
}
}
+564
View File
@@ -0,0 +1,564 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class File implements Extension\FileExtension
{
/**
* MIME types from the apache.org file. Some types are truncated.
*
* @var array Map of MIME types => file extension(s)
*
* @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
*/
private array $mimeTypes = [
'application/atom+xml' => 'atom',
'application/ecmascript' => 'ecma',
'application/emma+xml' => 'emma',
'application/epub+zip' => 'epub',
'application/java-archive' => 'jar',
'application/java-vm' => 'class',
'application/javascript' => 'js',
'application/json' => 'json',
'application/jsonml+json' => 'jsonml',
'application/lost+xml' => 'lostxml',
'application/mathml+xml' => 'mathml',
'application/mets+xml' => 'mets',
'application/mods+xml' => 'mods',
'application/mp4' => 'mp4s',
'application/msword' => ['doc', 'dot'],
'application/octet-stream' => [
'bin',
'dms',
'lrf',
'mar',
'so',
'dist',
'distz',
'pkg',
'bpk',
'dump',
'elc',
'deploy',
],
'application/ogg' => 'ogx',
'application/omdoc+xml' => 'omdoc',
'application/pdf' => 'pdf',
'application/pgp-encrypted' => 'pgp',
'application/pgp-signature' => ['asc', 'sig'],
'application/pkix-pkipath' => 'pkipath',
'application/pkixcmp' => 'pki',
'application/pls+xml' => 'pls',
'application/postscript' => ['ai', 'eps', 'ps'],
'application/pskc+xml' => 'pskcxml',
'application/rdf+xml' => 'rdf',
'application/reginfo+xml' => 'rif',
'application/rss+xml' => 'rss',
'application/rtf' => 'rtf',
'application/sbml+xml' => 'sbml',
'application/vnd.adobe.air-application-installer-package+zip' => 'air',
'application/vnd.adobe.xdp+xml' => 'xdp',
'application/vnd.adobe.xfdf' => 'xfdf',
'application/vnd.ahead.space' => 'ahead',
'application/vnd.dart' => 'dart',
'application/vnd.data-vision.rdz' => 'rdz',
'application/vnd.dece.data' => ['uvf', 'uvvf', 'uvd', 'uvvd'],
'application/vnd.dece.ttml+xml' => ['uvt', 'uvvt'],
'application/vnd.dece.unspecified' => ['uvx', 'uvvx'],
'application/vnd.dece.zip' => ['uvz', 'uvvz'],
'application/vnd.denovo.fcselayout-link' => 'fe_launch',
'application/vnd.dna' => 'dna',
'application/vnd.dolby.mlp' => 'mlp',
'application/vnd.dpgraph' => 'dpg',
'application/vnd.dreamfactory' => 'dfac',
'application/vnd.ds-keypoint' => 'kpxx',
'application/vnd.dvb.ait' => 'ait',
'application/vnd.dvb.service' => 'svc',
'application/vnd.dynageo' => 'geo',
'application/vnd.ecowin.chart' => 'mag',
'application/vnd.enliven' => 'nml',
'application/vnd.epson.esf' => 'esf',
'application/vnd.epson.msf' => 'msf',
'application/vnd.epson.quickanime' => 'qam',
'application/vnd.epson.salt' => 'slt',
'application/vnd.epson.ssf' => 'ssf',
'application/vnd.ezpix-album' => 'ez2',
'application/vnd.ezpix-package' => 'ez3',
'application/vnd.fdf' => 'fdf',
'application/vnd.fdsn.mseed' => 'mseed',
'application/vnd.fdsn.seed' => ['seed', 'dataless'],
'application/vnd.flographit' => 'gph',
'application/vnd.fluxtime.clip' => 'ftc',
'application/vnd.hal+xml' => 'hal',
'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx',
'application/vnd.ibm.minipay' => 'mpy',
'application/vnd.ibm.secure-container' => 'sc',
'application/vnd.iccprofile' => ['icc', 'icm'],
'application/vnd.igloader' => 'igl',
'application/vnd.immervision-ivp' => 'ivp',
'application/vnd.kde.karbon' => 'karbon',
'application/vnd.kde.kchart' => 'chrt',
'application/vnd.kde.kformula' => 'kfo',
'application/vnd.kde.kivio' => 'flw',
'application/vnd.kde.kontour' => 'kon',
'application/vnd.kde.kpresenter' => ['kpr', 'kpt'],
'application/vnd.kde.kspread' => 'ksp',
'application/vnd.kde.kword' => ['kwd', 'kwt'],
'application/vnd.kenameaapp' => 'htke',
'application/vnd.kidspiration' => 'kia',
'application/vnd.kinar' => ['kne', 'knp'],
'application/vnd.koan' => ['skp', 'skd', 'skt', 'skm'],
'application/vnd.kodak-descriptor' => 'sse',
'application/vnd.las.las+xml' => 'lasxml',
'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
'application/vnd.lotus-1-2-3' => '123',
'application/vnd.lotus-approach' => 'apr',
'application/vnd.lotus-freelance' => 'pre',
'application/vnd.lotus-notes' => 'nsf',
'application/vnd.lotus-organizer' => 'org',
'application/vnd.lotus-screencam' => 'scm',
'application/vnd.mozilla.xul+xml' => 'xul',
'application/vnd.ms-artgalry' => 'cil',
'application/vnd.ms-cab-compressed' => 'cab',
'application/vnd.ms-excel' => [
'xls',
'xlm',
'xla',
'xlc',
'xlt',
'xlw',
],
'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam',
'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb',
'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
'application/vnd.ms-excel.template.macroenabled.12' => 'xltm',
'application/vnd.ms-fontobject' => 'eot',
'application/vnd.ms-htmlhelp' => 'chm',
'application/vnd.ms-ims' => 'ims',
'application/vnd.ms-lrm' => 'lrm',
'application/vnd.ms-officetheme' => 'thmx',
'application/vnd.ms-pki.seccat' => 'cat',
'application/vnd.ms-pki.stl' => 'stl',
'application/vnd.ms-powerpoint' => ['ppt', 'pps', 'pot'],
'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam',
'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm',
'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm',
'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm',
'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm',
'application/vnd.ms-project' => ['mpp', 'mpt'],
'application/vnd.ms-word.document.macroenabled.12' => 'docm',
'application/vnd.ms-word.template.macroenabled.12' => 'dotm',
'application/vnd.ms-works' => ['wps', 'wks', 'wcm', 'wdb'],
'application/vnd.ms-wpl' => 'wpl',
'application/vnd.ms-xpsdocument' => 'xps',
'application/vnd.mseq' => 'mseq',
'application/vnd.musician' => 'mus',
'application/vnd.oasis.opendocument.chart' => 'odc',
'application/vnd.oasis.opendocument.chart-template' => 'otc',
'application/vnd.oasis.opendocument.database' => 'odb',
'application/vnd.oasis.opendocument.formula' => 'odf',
'application/vnd.oasis.opendocument.formula-template' => 'odft',
'application/vnd.oasis.opendocument.graphics' => 'odg',
'application/vnd.oasis.opendocument.graphics-template' => 'otg',
'application/vnd.oasis.opendocument.image' => 'odi',
'application/vnd.oasis.opendocument.image-template' => 'oti',
'application/vnd.oasis.opendocument.presentation' => 'odp',
'application/vnd.oasis.opendocument.presentation-template' => 'otp',
'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
'application/vnd.oasis.opendocument.text' => 'odt',
'application/vnd.oasis.opendocument.text-master' => 'odm',
'application/vnd.oasis.opendocument.text-template' => 'ott',
'application/vnd.oasis.opendocument.text-web' => 'oth',
'application/vnd.olpc-sugar' => 'xo',
'application/vnd.oma.dd2+xml' => 'dd2',
'application/vnd.openofficeorg.extension' => 'oxt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx',
'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
'application/vnd.pvi.ptid1' => 'ptid',
'application/vnd.quark.quarkxpress' => [
'qxd',
'qxt',
'qwd',
'qwt',
'qxl',
'qxb',
],
'application/vnd.realvnc.bed' => 'bed',
'application/vnd.recordare.musicxml' => 'mxl',
'application/vnd.recordare.musicxml+xml' => 'musicxml',
'application/vnd.rig.cryptonote' => 'cryptonote',
'application/vnd.rim.cod' => 'cod',
'application/vnd.rn-realmedia' => 'rm',
'application/vnd.rn-realmedia-vbr' => 'rmvb',
'application/vnd.route66.link66+xml' => 'link66',
'application/vnd.sailingtracker.track' => 'st',
'application/vnd.seemail' => 'see',
'application/vnd.sema' => 'sema',
'application/vnd.semd' => 'semd',
'application/vnd.semf' => 'semf',
'application/vnd.shana.informed.formdata' => 'ifm',
'application/vnd.shana.informed.formtemplate' => 'itp',
'application/vnd.shana.informed.interchange' => 'iif',
'application/vnd.shana.informed.package' => 'ipk',
'application/vnd.simtech-mindmapper' => ['twd', 'twds'],
'application/vnd.smaf' => 'mmf',
'application/vnd.stepmania.stepchart' => 'sm',
'application/vnd.sun.xml.calc' => 'sxc',
'application/vnd.sun.xml.calc.template' => 'stc',
'application/vnd.sun.xml.draw' => 'sxd',
'application/vnd.sun.xml.draw.template' => 'std',
'application/vnd.sun.xml.impress' => 'sxi',
'application/vnd.sun.xml.impress.template' => 'sti',
'application/vnd.sun.xml.math' => 'sxm',
'application/vnd.sun.xml.writer' => 'sxw',
'application/vnd.sun.xml.writer.global' => 'sxg',
'application/vnd.sun.xml.writer.template' => 'stw',
'application/vnd.sus-calendar' => ['sus', 'susp'],
'application/vnd.svd' => 'svd',
'application/vnd.symbian.install' => ['sis', 'sisx'],
'application/vnd.syncml+xml' => 'xsm',
'application/vnd.syncml.dm+wbxml' => 'bdm',
'application/vnd.syncml.dm+xml' => 'xdm',
'application/vnd.tao.intent-module-archive' => 'tao',
'application/vnd.tcpdump.pcap' => ['pcap', 'cap', 'dmp'],
'application/vnd.tmobile-livetv' => 'tmo',
'application/vnd.trid.tpt' => 'tpt',
'application/vnd.triscape.mxs' => 'mxs',
'application/vnd.trueapp' => 'tra',
'application/vnd.ufdl' => ['ufd', 'ufdl'],
'application/vnd.uiq.theme' => 'utz',
'application/vnd.umajin' => 'umj',
'application/vnd.unity' => 'unityweb',
'application/vnd.uoml+xml' => 'uoml',
'application/vnd.vcx' => 'vcx',
'application/vnd.visio' => ['vsd', 'vst', 'vss', 'vsw'],
'application/vnd.visionary' => 'vis',
'application/vnd.vsf' => 'vsf',
'application/vnd.wap.wbxml' => 'wbxml',
'application/vnd.wap.wmlc' => 'wmlc',
'application/vnd.wap.wmlscriptc' => 'wmlsc',
'application/vnd.webturbo' => 'wtb',
'application/vnd.wolfram.player' => 'nbp',
'application/vnd.wordperfect' => 'wpd',
'application/vnd.wqd' => 'wqd',
'application/vnd.wt.stf' => 'stf',
'application/vnd.xara' => 'xar',
'application/vnd.xfdl' => 'xfdl',
'application/voicexml+xml' => 'vxml',
'application/widget' => 'wgt',
'application/winhlp' => 'hlp',
'application/wsdl+xml' => 'wsdl',
'application/wspolicy+xml' => 'wspolicy',
'application/x-7z-compressed' => '7z',
'application/x-bittorrent' => 'torrent',
'application/x-blorb' => ['blb', 'blorb'],
'application/x-bzip' => 'bz',
'application/x-cdlink' => 'vcd',
'application/x-cfs-compressed' => 'cfs',
'application/x-chat' => 'chat',
'application/x-chess-pgn' => 'pgn',
'application/x-conference' => 'nsc',
'application/x-cpio' => 'cpio',
'application/x-csh' => 'csh',
'application/x-debian-package' => ['deb', 'udeb'],
'application/x-dgc-compressed' => 'dgc',
'application/x-director' => [
'dir',
'dcr',
'dxr',
'cst',
'cct',
'cxt',
'w3d',
'fgd',
'swa',
],
'application/x-font-ttf' => ['ttf', 'ttc'],
'application/x-font-type1' => ['pfa', 'pfb', 'pfm', 'afm'],
'application/x-font-woff' => 'woff',
'application/x-freearc' => 'arc',
'application/x-futuresplash' => 'spl',
'application/x-gca-compressed' => 'gca',
'application/x-glulx' => 'ulx',
'application/x-gnumeric' => 'gnumeric',
'application/x-gramps-xml' => 'gramps',
'application/x-gtar' => 'gtar',
'application/x-hdf' => 'hdf',
'application/x-install-instructions' => 'install',
'application/x-iso9660-image' => 'iso',
'application/x-java-jnlp-file' => 'jnlp',
'application/x-latex' => 'latex',
'application/x-lzh-compressed' => ['lzh', 'lha'],
'application/x-mie' => 'mie',
'application/x-mobipocket-ebook' => ['prc', 'mobi'],
'application/x-ms-application' => 'application',
'application/x-ms-shortcut' => 'lnk',
'application/x-ms-wmd' => 'wmd',
'application/x-ms-wmz' => 'wmz',
'application/x-ms-xbap' => 'xbap',
'application/x-msaccess' => 'mdb',
'application/x-msbinder' => 'obd',
'application/x-mscardfile' => 'crd',
'application/x-msclip' => 'clp',
'application/x-msdownload' => ['exe', 'dll', 'com', 'bat', 'msi'],
'application/x-msmediaview' => [
'mvb',
'm13',
'm14',
],
'application/x-msmetafile' => ['wmf', 'wmz', 'emf', 'emz'],
'application/x-rar-compressed' => 'rar',
'application/x-research-info-systems' => 'ris',
'application/x-sh' => 'sh',
'application/x-shar' => 'shar',
'application/x-shockwave-flash' => 'swf',
'application/x-silverlight-app' => 'xap',
'application/x-sql' => 'sql',
'application/x-stuffit' => 'sit',
'application/x-stuffitx' => 'sitx',
'application/x-subrip' => 'srt',
'application/x-sv4cpio' => 'sv4cpio',
'application/x-sv4crc' => 'sv4crc',
'application/x-t3vm-image' => 't3',
'application/x-tads' => 'gam',
'application/x-tar' => 'tar',
'application/x-tcl' => 'tcl',
'application/x-tex' => 'tex',
'application/x-tex-tfm' => 'tfm',
'application/x-texinfo' => ['texinfo', 'texi'],
'application/x-tgif' => 'obj',
'application/x-ustar' => 'ustar',
'application/x-wais-source' => 'src',
'application/x-x509-ca-cert' => ['der', 'crt'],
'application/x-xfig' => 'fig',
'application/x-xliff+xml' => 'xlf',
'application/x-xpinstall' => 'xpi',
'application/x-xz' => 'xz',
'application/x-zmachine' => 'z1',
'application/xaml+xml' => 'xaml',
'application/xcap-diff+xml' => 'xdf',
'application/xenc+xml' => 'xenc',
'application/xhtml+xml' => ['xhtml', 'xht'],
'application/xml' => ['xml', 'xsl'],
'application/xml-dtd' => 'dtd',
'application/xop+xml' => 'xop',
'application/xproc+xml' => 'xpl',
'application/xslt+xml' => 'xslt',
'application/xspf+xml' => 'xspf',
'application/xv+xml' => ['mxml', 'xhvml', 'xvml', 'xvm'],
'application/yang' => 'yang',
'application/yin+xml' => 'yin',
'application/zip' => 'zip',
'audio/adpcm' => 'adp',
'audio/basic' => ['au', 'snd'],
'audio/midi' => ['mid', 'midi', 'kar', 'rmi'],
'audio/mp4' => 'mp4a',
'audio/mpeg' => [
'mpga',
'mp2',
'mp2a',
'mp3',
'm2a',
'm3a',
],
'audio/ogg' => ['oga', 'ogg', 'spx'],
'audio/vnd.dece.audio' => ['uva', 'uvva'],
'audio/vnd.rip' => 'rip',
'audio/webm' => 'weba',
'audio/x-aac' => 'aac',
'audio/x-aiff' => ['aif', 'aiff', 'aifc'],
'audio/x-caf' => 'caf',
'audio/x-flac' => 'flac',
'audio/x-matroska' => 'mka',
'audio/x-mpegurl' => 'm3u',
'audio/x-ms-wax' => 'wax',
'audio/x-ms-wma' => 'wma',
'audio/x-pn-realaudio' => ['ram', 'ra'],
'audio/x-pn-realaudio-plugin' => 'rmp',
'audio/x-wav' => 'wav',
'audio/xm' => 'xm',
'image/bmp' => 'bmp',
'image/cgm' => 'cgm',
'image/g3fax' => 'g3',
'image/gif' => 'gif',
'image/ief' => 'ief',
'image/jpeg' => ['jpeg', 'jpg', 'jpe'],
'image/ktx' => 'ktx',
'image/png' => 'png',
'image/prs.btif' => 'btif',
'image/sgi' => 'sgi',
'image/svg+xml' => ['svg', 'svgz'],
'image/tiff' => ['tiff', 'tif'],
'image/vnd.adobe.photoshop' => 'psd',
'image/vnd.dece.graphic' => ['uvi', 'uvvi', 'uvg', 'uvvg'],
'image/vnd.dvb.subtitle' => 'sub',
'image/vnd.djvu' => ['djvu', 'djv'],
'image/vnd.dwg' => 'dwg',
'image/vnd.dxf' => 'dxf',
'image/vnd.fastbidsheet' => 'fbs',
'image/vnd.fpx' => 'fpx',
'image/vnd.fst' => 'fst',
'image/vnd.fujixerox.edmics-mmr' => 'mmr',
'image/vnd.fujixerox.edmics-rlc' => 'rlc',
'image/vnd.ms-modi' => 'mdi',
'image/vnd.ms-photo' => 'wdp',
'image/vnd.net-fpx' => 'npx',
'image/vnd.wap.wbmp' => 'wbmp',
'image/vnd.xiff' => 'xif',
'image/webp' => 'webp',
'image/x-3ds' => '3ds',
'image/x-cmu-raster' => 'ras',
'image/x-cmx' => 'cmx',
'image/x-freehand' => ['fh', 'fhc', 'fh4', 'fh5', 'fh7'],
'image/x-icon' => 'ico',
'image/x-mrsid-image' => 'sid',
'image/x-pcx' => 'pcx',
'image/x-pict' => ['pic', 'pct'],
'image/x-portable-anymap' => 'pnm',
'image/x-portable-bitmap' => 'pbm',
'image/x-portable-graymap' => 'pgm',
'image/x-portable-pixmap' => 'ppm',
'image/x-rgb' => 'rgb',
'image/x-tga' => 'tga',
'image/x-xbitmap' => 'xbm',
'image/x-xpixmap' => 'xpm',
'image/x-xwindowdump' => 'xwd',
'message/rfc822' => ['eml', 'mime'],
'model/iges' => ['igs', 'iges'],
'model/mesh' => ['msh', 'mesh', 'silo'],
'model/vnd.collada+xml' => 'dae',
'model/vnd.dwf' => 'dwf',
'model/vnd.gdl' => 'gdl',
'model/vnd.gtw' => 'gtw',
'model/vnd.mts' => 'mts',
'model/vnd.vtu' => 'vtu',
'model/vrml' => ['wrl', 'vrml'],
'model/x3d+binary' => 'x3db',
'model/x3d+vrml' => 'x3dv',
'model/x3d+xml' => 'x3d',
'text/cache-manifest' => 'appcache',
'text/calendar' => ['ics', 'ifb'],
'text/css' => 'css',
'text/csv' => 'csv',
'text/html' => ['html', 'htm'],
'text/n3' => 'n3',
'text/plain' => [
'txt',
'text',
'conf',
'def',
'list',
'log',
'in',
],
'text/prs.lines.tag' => 'dsc',
'text/richtext' => 'rtx',
'text/sgml' => ['sgml', 'sgm'],
'text/tab-separated-values' => 'tsv',
'text/troff' => [
't',
'tr',
'roff',
'man',
'me',
'ms',
],
'text/turtle' => 'ttl',
'text/uri-list' => ['uri', 'uris', 'urls'],
'text/vcard' => 'vcard',
'text/vnd.curl' => 'curl',
'text/vnd.curl.dcurl' => 'dcurl',
'text/vnd.curl.scurl' => 'scurl',
'text/vnd.curl.mcurl' => 'mcurl',
'text/vnd.dvb.subtitle' => 'sub',
'text/vnd.fly' => 'fly',
'text/vnd.fmi.flexstor' => 'flx',
'text/vnd.graphviz' => 'gv',
'text/vnd.in3d.3dml' => '3dml',
'text/vnd.in3d.spot' => 'spot',
'text/vnd.sun.j2me.app-descriptor' => 'jad',
'text/vnd.wap.wml' => 'wml',
'text/vnd.wap.wmlscript' => 'wmls',
'text/x-asm' => ['s', 'asm'],
'text/x-fortran' => ['f', 'for', 'f77', 'f90'],
'text/x-java-source' => 'java',
'text/x-opml' => 'opml',
'text/x-pascal' => ['p', 'pas'],
'text/x-nfo' => 'nfo',
'text/x-setext' => 'etx',
'text/x-sfv' => 'sfv',
'text/x-uuencode' => 'uu',
'text/x-vcalendar' => 'vcs',
'text/x-vcard' => 'vcf',
'video/3gpp' => '3gp',
'video/3gpp2' => '3g2',
'video/h261' => 'h261',
'video/h263' => 'h263',
'video/h264' => 'h264',
'video/jpeg' => 'jpgv',
'video/jpm' => ['jpm', 'jpgm'],
'video/mj2' => 'mj2',
'video/mp4' => 'mp4',
'video/mpeg' => ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v'],
'video/ogg' => 'ogv',
'video/quicktime' => ['qt', 'mov'],
'video/vnd.dece.hd' => ['uvh', 'uvvh'],
'video/vnd.dece.mobile' => ['uvm', 'uvvm'],
'video/vnd.dece.pd' => ['uvp', 'uvvp'],
'video/vnd.dece.sd' => ['uvs', 'uvvs'],
'video/vnd.dece.video' => ['uvv', 'uvvv'],
'video/vnd.dvb.file' => 'dvb',
'video/vnd.fvt' => 'fvt',
'video/vnd.mpegurl' => ['mxu', 'm4u'],
'video/vnd.ms-playready.media.pyv' => 'pyv',
'video/vnd.uvvu.mp4' => ['uvu', 'uvvu'],
'video/vnd.vivo' => 'viv',
'video/webm' => 'webm',
'video/x-f4v' => 'f4v',
'video/x-fli' => 'fli',
'video/x-flv' => 'flv',
'video/x-m4v' => 'm4v',
'video/x-matroska' => ['mkv', 'mk3d', 'mks'],
'video/x-mng' => 'mng',
'video/x-ms-asf' => ['asf', 'asx'],
'video/x-ms-vob' => 'vob',
'video/x-ms-wm' => 'wm',
'video/x-ms-wmv' => 'wmv',
'video/x-ms-wmx' => 'wmx',
'video/x-ms-wvx' => 'wvx',
'video/x-msvideo' => 'avi',
'video/x-sgi-movie' => 'movie',
];
public function mimeType(): string
{
return array_rand($this->mimeTypes, 1);
}
public function extension(): string
{
$extension = $this->mimeTypes[array_rand($this->mimeTypes, 1)];
return is_array($extension) ? $extension[array_rand($extension, 1)] : $extension;
}
public function filePath(): string
{
return tempnam(sys_get_temp_dir(), 'faker');
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Number implements Extension\NumberExtension
{
public function numberBetween(int $min = 0, int $max = 2147483647): int
{
$int1 = min($min, $max);
$int2 = max($min, $max);
return mt_rand($int1, $int2);
}
public function randomDigit(): int
{
return $this->numberBetween(0, 9);
}
public function randomDigitNot(int $except): int
{
$result = $this->numberBetween(0, 8);
if ($result >= $except) {
++$result;
}
return $result;
}
public function randomDigitNotZero(): int
{
return $this->numberBetween(1, 9);
}
public function randomFloat(?int $nbMaxDecimals = null, float $min = 0, ?float $max = null): float
{
if (null === $nbMaxDecimals) {
$nbMaxDecimals = $this->randomDigit();
}
if (null === $max) {
$max = $this->randomNumber();
if ($min > $max) {
$max = $min;
}
}
if ($min > $max) {
$tmp = $min;
$min = $max;
$max = $tmp;
}
return round($min + $this->numberBetween() / mt_getrandmax() * ($max - $min), $nbMaxDecimals);
}
public function randomNumber(?int $nbDigits = null, bool $strict = false): int
{
if (null === $nbDigits) {
$nbDigits = $this->randomDigitNotZero();
}
$max = 10 ** $nbDigits - 1;
if ($max > mt_getrandmax()) {
throw new \InvalidArgumentException('randomNumber() can only generate numbers up to mt_getrandmax()');
}
if ($strict) {
return $this->numberBetween(10 ** ($nbDigits - 1), $max);
}
return $this->numberBetween(0, $max);
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Uuid implements Extension\UuidExtension
{
private Extension\NumberExtension $numberExtension;
public function __construct(?Extension\NumberExtension $numberExtension = null)
{
$this->numberExtension = $numberExtension ?: new Number();
}
public function uuid3(): string
{
// fix for compatibility with 32bit architecture; each mt_rand call is restricted to 32bit
// two such calls will cause 64bits of randomness regardless of architecture
$seed = $this->numberExtension->numberBetween(0, 2147483647) . '#' . $this->numberExtension->numberBetween(0, 2147483647);
// Hash the seed and convert to a byte array
$val = md5($seed, true);
$byte = array_values(unpack('C16', $val));
// extract fields from byte array
$tLo = ($byte[0] << 24) | ($byte[1] << 16) | ($byte[2] << 8) | $byte[3];
$tMi = ($byte[4] << 8) | $byte[5];
$tHi = ($byte[6] << 8) | $byte[7];
$csLo = $byte[9];
$csHi = $byte[8] & 0x3f | (1 << 7);
// correct byte order for big edian architecture
if (pack('L', 0x6162797A) == pack('N', 0x6162797A)) {
$tLo = (($tLo & 0x000000ff) << 24) | (($tLo & 0x0000ff00) << 8)
| (($tLo & 0x00ff0000) >> 8) | (($tLo & 0xff000000) >> 24);
$tMi = (($tMi & 0x00ff) << 8) | (($tMi & 0xff00) >> 8);
$tHi = (($tHi & 0x00ff) << 8) | (($tHi & 0xff00) >> 8);
}
// apply version number
$tHi &= 0x0fff;
$tHi |= (3 << 12);
// cast to string
return sprintf(
'%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x',
$tLo,
$tMi,
$tHi,
$csHi,
$csLo,
$byte[10],
$byte[11],
$byte[12],
$byte[13],
$byte[14],
$byte[15],
);
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
use Faker\Provider\DateTime;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Version implements Extension\VersionExtension
{
private Extension\NumberExtension $numberExtension;
/**
* @var string[]
*/
private array $semverCommonPreReleaseIdentifiers = ['alpha', 'beta', 'rc'];
public function __construct(?Extension\NumberExtension $numberExtension = null)
{
$this->numberExtension = $numberExtension ?: new Number();
}
/**
* Represents v2.0.0 of the semantic versioning: https://semver.org/spec/v2.0.0.html
*/
public function semver(bool $preRelease = false, bool $build = false): string
{
return sprintf(
'%d.%d.%d%s%s',
$this->numberExtension->numberBetween(0, 9),
$this->numberExtension->numberBetween(0, 99),
$this->numberExtension->numberBetween(0, 99),
$preRelease && $this->numberExtension->numberBetween(0, 1) === 1 ? '-' . $this->semverPreReleaseIdentifier() : '',
$build && $this->numberExtension->numberBetween(0, 1) === 1 ? '+' . $this->semverBuildIdentifier() : '',
);
}
/**
* Common pre-release identifier
*/
private function semverPreReleaseIdentifier(): string
{
$ident = Extension\Helper::randomElement($this->semverCommonPreReleaseIdentifiers);
if ($this->numberExtension->numberBetween(0, 1) !== 1) {
return $ident;
}
return $ident . '.' . $this->numberExtension->numberBetween(1, 99);
}
/**
* Common random build identifier
*/
private function semverBuildIdentifier(): string
{
if ($this->numberExtension->numberBetween(0, 1) === 1) {
// short git revision syntax: https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection
return substr(sha1(Extension\Helper::lexify('??????')), 0, 7);
}
// date syntax
return DateTime::date('YmdHis');
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace Faker;
/**
* This generator returns a default value for all called properties
* and methods.
*
* @mixin Generator
*
* @deprecated Use ChanceGenerator instead
*/
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
trigger_deprecation('fakerphp/faker', '1.16', 'Class "%s" is deprecated, use "%s" instead.', __CLASS__, ChanceGenerator::class);
$this->default = $default;
}
public function ext()
{
return $this;
}
/**
* @param string $attribute
*
* @deprecated Use a method instead.
*/
public function __get($attribute)
{
trigger_deprecation('fakerphp/faker', '1.14', 'Accessing property "%s" is deprecated, use "%s()" instead.', $attribute, $attribute);
return $this->default;
}
/**
* @param string $method
* @param array $attributes
*/
public function __call($method, $attributes)
{
return $this->default;
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace Faker;
class Documentor
{
protected $generator;
public function __construct(Generator $generator)
{
$this->generator = $generator;
}
/**
* @return array
*/
public function getFormatters()
{
$formatters = [];
$providers = array_reverse($this->generator->getProviders());
$providers[] = new Provider\Base($this->generator);
foreach ($providers as $provider) {
$providerClass = get_class($provider);
$formatters[$providerClass] = [];
$refl = new \ReflectionObject($provider);
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflmethod) {
if ($reflmethod->getDeclaringClass()->getName() == 'Faker\Provider\Base' && $providerClass != 'Faker\Provider\Base') {
continue;
}
$methodName = $reflmethod->name;
if ($reflmethod->isConstructor()) {
continue;
}
$parameters = [];
foreach ($reflmethod->getParameters() as $reflparameter) {
$parameter = '$' . $reflparameter->getName();
if ($reflparameter->isDefaultValueAvailable()) {
$parameter .= ' = ' . var_export($reflparameter->getDefaultValue(), true);
}
$parameters[] = $parameter;
}
$parameters = $parameters ? '(' . implode(', ', $parameters) . ')' : '';
try {
$example = $this->generator->format($methodName);
} catch (\InvalidArgumentException $e) {
$example = '';
}
if (is_array($example)) {
$example = "array('" . implode("', '", $example) . "')";
} elseif ($example instanceof \DateTime) {
$example = "DateTime('" . $example->format('Y-m-d H:i:s') . "')";
} elseif ($example instanceof Generator || $example instanceof UniqueGenerator) { // modifier
$example = '';
} else {
$example = var_export($example, true);
}
$formatters[$providerClass][$methodName . $parameters] = $example;
}
}
return $formatters;
}
}
@@ -0,0 +1,39 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface AddressExtension extends Extension
{
/**
* @example '791 Crist Parks, Sashabury, IL 86039-9874'
*/
public function address(): string;
/**
* Randomly return a real city name.
*/
public function city(): string;
/**
* @example 86039-9874
*/
public function postcode(): string;
/**
* @example 'Crist Parks'
*/
public function streetName(): string;
/**
* @example '791 Crist Parks'
*/
public function streetAddress(): string;
/**
* Randomly return a building number.
*/
public function buildingNumber(): string;
}
@@ -0,0 +1,41 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface BarcodeExtension extends Extension
{
/**
* Get a random EAN13 barcode.
*
* @example '4006381333931'
*/
public function ean13(): string;
/**
* Get a random EAN8 barcode.
*
* @example '73513537'
*/
public function ean8(): string;
/**
* Get a random ISBN-10 code
*
* @see http://en.wikipedia.org/wiki/International_Standard_Book_Number
*
* @example '4881416324'
*/
public function isbn10(): string;
/**
* Get a random ISBN-13 code
*
* @see http://en.wikipedia.org/wiki/International_Standard_Book_Number
*
* @example '9790404436093'
*/
public function isbn13(): string;
}
@@ -0,0 +1,30 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface BloodExtension extends Extension
{
/**
* Get an actual blood type
*
* @example 'AB'
*/
public function bloodType(): string;
/**
* Get a random resis value
*
* @example '+'
*/
public function bloodRh(): string;
/**
* Get a full blood group
*
* @example 'AB+'
*/
public function bloodGroup(): string;
}
@@ -0,0 +1,63 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface ColorExtension extends Extension
{
/**
* @example '#fa3cc2'
*/
public function hexColor(): string;
/**
* @example '#ff0044'
*/
public function safeHexColor(): string;
/**
* @example 'array(0,255,122)'
*
* @return int[]
*/
public function rgbColorAsArray(): array;
/**
* @example '0,255,122'
*/
public function rgbColor(): string;
/**
* @example 'rgb(0,255,122)'
*/
public function rgbCssColor(): string;
/**
* @example 'rgba(0,255,122,0.8)'
*/
public function rgbaCssColor(): string;
/**
* @example 'blue'
*/
public function safeColorName(): string;
/**
* @example 'NavajoWhite'
*/
public function colorName(): string;
/**
* @example '340,50,20'
*/
public function hslColor(): string;
/**
* @example array(340, 50, 20)
*
* @return int[]
*/
public function hslColorAsArray(): array;
}
@@ -0,0 +1,21 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface CompanyExtension extends Extension
{
/**
* @example 'Acme Ltd'
*/
public function company(): string;
/**
* @example 'Ltd'
*/
public function companySuffix(): string;
public function jobTitle(): string;
}
@@ -0,0 +1,14 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface CountryExtension extends Extension
{
/**
* @example 'Japan'
*/
public function country(): string;
}
@@ -0,0 +1,242 @@
<?php
namespace Faker\Extension;
/**
* FakerPHP extension for Date-related randomization.
*
* Functions accepting a date string use the `strtotime()` function internally.
*
* @experimental
*
* @since 1.20.0
*/
interface DateTimeExtension
{
/**
* Get a DateTime object between January 1, 1970, and `$until` (defaults to "now").
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone zone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*
* @example DateTime('2005-08-16 20:39:21')
*/
public function dateTime($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a DateTime object for a date between January 1, 0001, and now.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone zone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @example DateTime('1265-03-22 21:15:52')
*
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeAD($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a DateTime object a random date between `$from` and `$until`.
* Accepts date strings that can be recognized by `strtotime()`.
*
* @param \DateTime|string $from defaults to 30 years ago
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone zone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeBetween($from = '-30 years', $until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a DateTime object based on a random date between `$from` and an interval.
* Accepts date string that can be recognized by `strtotime()`.
*
* @param \DateTime|int|string $from defaults to 30 years ago
* @param string $interval defaults to 5 days after
* @param string|null $timezone zone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeInInterval($from = '-30 years', string $interval = '+5 days', ?string $timezone = null): \DateTime;
/**
* Get a date time object somewhere inside the current week.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone zone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeThisWeek($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a date time object somewhere inside the current month.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeThisMonth($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a date time object somewhere inside the current year.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeThisYear($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a date time object somewhere inside the current decade.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeThisDecade($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a date time object somewhere inside the current century.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeThisCentury($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a date string between January 1, 1970, and `$until`.
*
* @param string $format DateTime format
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @see https://www.php.net/manual/en/datetime.format.php
*/
public function date(string $format = 'Y-m-d', $until = 'now'): string;
/**
* Get a time string (24h format by default).
*
* @param string $format DateTime format
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @see https://www.php.net/manual/en/datetime.format.php
*/
public function time(string $format = 'H:i:s', $until = 'now'): string;
/**
* Get a UNIX (POSIX-compatible) timestamp between January 1, 1970, and `$until`.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*/
public function unixTime($until = 'now'): int;
/**
* Get a date string according to the ISO-8601 standard.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*/
public function iso8601($until = 'now'): string;
/**
* Get a string containing either "am" or "pm".
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @example 'am'
*/
public function amPm($until = 'now'): string;
/**
* Get a localized random day of the month.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @example '16'
*/
public function dayOfMonth($until = 'now'): string;
/**
* Get a localized random day of the week.
*
* Uses internal DateTime formatting, hence PHP's internal locale will be used (change using `setlocale()`).
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @example 'Tuesday'
*
* @see setlocale
* @see https://www.php.net/manual/en/function.setlocale.php Set a different output language
*/
public function dayOfWeek($until = 'now'): string;
/**
* Get a random month (numbered).
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @example '7'
*/
public function month($until = 'now'): string;
/**
* Get a random month.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @see setlocale
* @see https://www.php.net/manual/en/function.setlocale.php Set a different output language
*
* @example 'September'
*/
public function monthName($until = 'now'): string;
/**
* Get a random year between 1970 and `$until`.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @example '1987'
*/
public function year($until = 'now'): string;
/**
* Get a random century, formatted as Roman numerals.
*
* @example 'XVII'
*/
public function century(): string;
/**
* Get a random timezone, uses `\DateTimeZone::listIdentifiers()` internally.
*
* @param string|null $countryCode two-letter ISO 3166-1 compatible country code
*
* @example 'Europe/Rome'
*/
public function timezone(?string $countryCode = null): string;
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Faker\Extension;
/**
* An extension is the only way to add new functionality to Faker.
*
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface Extension
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class ExtensionNotFound extends \LogicException
{
}
@@ -0,0 +1,28 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface FileExtension extends Extension
{
/**
* Get a random MIME type
*
* @example 'video/avi'
*/
public function mimeType(): string;
/**
* Get a random file extension (without a dot)
*
* @example avi
*/
public function extension(): string;
/**
* Get a full path to a new real file on the system.
*/
public function filePath(): string;
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Faker\Extension;
use Faker\Generator;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface GeneratorAwareExtension extends Extension
{
/**
* This method MUST be implemented in such a way as to retain the
* immutability of the extension, and MUST return an instance that has the
* new Generator.
*/
public function withGenerator(Generator $generator): Extension;
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Faker\Extension;
use Faker\Generator;
/**
* A helper trait to be used with GeneratorAwareExtension.
*/
trait GeneratorAwareExtensionTrait
{
/**
* @var Generator|null
*/
private $generator;
/**
* @return static
*/
public function withGenerator(Generator $generator): Extension
{
$instance = clone $this;
$instance->generator = $generator;
return $instance;
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
namespace Faker\Extension;
/**
* A class with some methods that may make building extensions easier.
*
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Helper
{
/**
* Returns a random element from a passed array.
*/
public static function randomElement(array $array)
{
if ($array === []) {
return null;
}
return $array[array_rand($array, 1)];
}
/**
* Replaces all hash sign ('#') occurrences with a random number
* Replaces all percentage sign ('%') occurrences with a non-zero number.
*
* @param string $string String that needs to bet parsed
*/
public static function numerify(string $string): string
{
// instead of using randomDigit() several times, which is slow,
// count the number of hashes and generate once a large number
$toReplace = [];
if (($pos = strpos($string, '#')) !== false) {
for ($i = $pos, $last = strrpos($string, '#', $pos) + 1; $i < $last; ++$i) {
if ($string[$i] === '#') {
$toReplace[] = $i;
}
}
}
if ($nbReplacements = count($toReplace)) {
$maxAtOnce = strlen((string) mt_getrandmax()) - 1;
$numbers = '';
$i = 0;
while ($i < $nbReplacements) {
$size = min($nbReplacements - $i, $maxAtOnce);
$numbers .= str_pad((string) mt_rand(0, 10 ** $size - 1), $size, '0', STR_PAD_LEFT);
$i += $size;
}
for ($i = 0; $i < $nbReplacements; ++$i) {
$string[$toReplace[$i]] = $numbers[$i];
}
}
return self::replaceWildcard($string, '%', static function () {
return mt_rand(1, 9);
});
}
/**
* Replaces all question mark ('?') occurrences with a random letter.
*
* @param string $string String that needs to bet parsed
*/
public static function lexify(string $string): string
{
return self::replaceWildcard($string, '?', static function () {
return chr(mt_rand(97, 122));
});
}
/**
* Replaces hash signs ('#') and question marks ('?') with random numbers and letters
* An asterisk ('*') is replaced with either a random number or a random letter.
*
* @param string $string String that needs to bet parsed
*/
public static function bothify(string $string): string
{
$string = self::replaceWildcard($string, '*', static function () {
return mt_rand(0, 1) === 1 ? '#' : '?';
});
return self::lexify(self::numerify($string));
}
private static function replaceWildcard(string $string, string $wildcard, callable $callback): string
{
if (($pos = strpos($string, $wildcard)) === false) {
return $string;
}
for ($i = $pos, $last = strrpos($string, $wildcard, $pos) + 1; $i < $last; ++$i) {
if ($string[$i] === $wildcard) {
$string[$i] = call_user_func($callback);
}
}
return $string;
}
}
@@ -0,0 +1,53 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface NumberExtension extends Extension
{
/**
* Returns a random number between $int1 and $int2 (any order)
*
* @param int $min default to 0
* @param int $max defaults to 32 bit max integer, ie 2147483647
*
* @example 79907610
*/
public function numberBetween(int $min, int $max): int;
/**
* Returns a random number between 0 and 9
*/
public function randomDigit(): int;
/**
* Generates a random digit, which cannot be $except
*/
public function randomDigitNot(int $except): int;
/**
* Returns a random number between 1 and 9
*/
public function randomDigitNotZero(): int;
/**
* Return a random float number
*
* @example 48.8932
*/
public function randomFloat(?int $nbMaxDecimals, float $min, ?float $max): float;
/**
* Returns a random integer with 0 to $nbDigits digits.
*
* The maximum value returned is mt_getrandmax()
*
* @param int|null $nbDigits Defaults to a random number between 1 and 9
* @param bool $strict Whether the returned number should have exactly $nbDigits
*
* @example 79907610
*/
public function randomNumber(?int $nbDigits, bool $strict): int;
}
@@ -0,0 +1,52 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface PersonExtension extends Extension
{
public const GENDER_FEMALE = 'female';
public const GENDER_MALE = 'male';
/**
* @param string|null $gender 'male', 'female' or null for any
*
* @example 'John Doe'
*/
public function name(?string $gender = null): string;
/**
* @param string|null $gender 'male', 'female' or null for any
*
* @example 'John'
*/
public function firstName(?string $gender = null): string;
public function firstNameMale(): string;
public function firstNameFemale(): string;
/**
* @example 'Doe'
*/
public function lastName(): string;
/**
* @example 'Mrs.'
*
* @param string|null $gender 'male', 'female' or null for any
*/
public function title(?string $gender = null): string;
/**
* @example 'Mr.'
*/
public function titleMale(): string;
/**
* @example 'Mrs.'
*/
public function titleFemale(): string;
}
@@ -0,0 +1,19 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface PhoneNumberExtension extends Extension
{
/**
* @example '555-123-546'
*/
public function phoneNumber(): string;
/**
* @example +27113456789
*/
public function e164PhoneNumber(): string;
}
@@ -0,0 +1,16 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface UuidExtension extends Extension
{
/**
* Generate name based md5 UUID (version 3).
*
* @example '7e57d004-2b97-0e7a-b45f-5387367791cd'
*/
public function uuid3(): string;
}
@@ -0,0 +1,21 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface VersionExtension extends Extension
{
/**
* Get a version number in semantic versioning syntax 2.0.0. (https://semver.org/spec/v2.0.0.html)
*
* @param bool $preRelease Pre release parts may be randomly included
* @param bool $build Build parts may be randomly included
*
* @example 1.0.0
* @example 1.0.0-alpha.1
* @example 1.0.0-alpha.1+b71f04d
*/
public function semver(bool $preRelease = false, bool $build = false): string;
}
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace Faker;
class Factory
{
public const DEFAULT_LOCALE = 'en_US';
protected static $defaultProviders = ['Address', 'Barcode', 'Biased', 'Color', 'Company', 'DateTime', 'File', 'HtmlLorem', 'Image', 'Internet', 'Lorem', 'Medical', 'Miscellaneous', 'Payment', 'Person', 'PhoneNumber', 'Text', 'UserAgent', 'Uuid'];
/**
* Create a new generator
*
* @param string $locale
*
* @return Generator
*/
public static function create($locale = self::DEFAULT_LOCALE)
{
$generator = new Generator();
foreach (static::$defaultProviders as $provider) {
$providerClassName = self::getProviderClassname($provider, $locale);
$generator->addProvider(new $providerClassName($generator));
}
return $generator;
}
/**
* @param string $provider
* @param string $locale
*
* @return string
*/
protected static function getProviderClassname($provider, $locale = '')
{
if ($providerClass = self::findProviderClassname($provider, $locale)) {
return $providerClass;
}
// fallback to default locale
if ($providerClass = self::findProviderClassname($provider, static::DEFAULT_LOCALE)) {
return $providerClass;
}
// fallback to no locale
if ($providerClass = self::findProviderClassname($provider)) {
return $providerClass;
}
throw new \InvalidArgumentException(sprintf('Unable to find provider "%s" with locale "%s"', $provider, $locale));
}
/**
* @param string $provider
* @param string $locale
*
* @return string|null
*/
protected static function findProviderClassname($provider, $locale = '')
{
$providerClass = 'Faker\\' . ($locale ? sprintf('Provider\%s\%s', $locale, $provider) : sprintf('Provider\%s', $provider));
if (class_exists($providerClass, true)) {
return $providerClass;
}
return null;
}
}
+985
View File
@@ -0,0 +1,985 @@
<?php
namespace Faker;
use Faker\Container\ContainerInterface;
/**
* @property string $citySuffix
*
* @method string citySuffix()
*
* @property string $streetSuffix
*
* @method string streetSuffix()
*
* @property string $buildingNumber
*
* @method string buildingNumber()
*
* @property string $city
*
* @method string city()
*
* @property string $streetName
*
* @method string streetName()
*
* @property string $streetAddress
*
* @method string streetAddress()
*
* @property string $postcode
*
* @method string postcode()
*
* @property string $address
*
* @method string address()
*
* @property string $country
*
* @method string country()
*
* @property float $latitude
*
* @method float latitude($min = -90, $max = 90)
*
* @property float $longitude
*
* @method float longitude($min = -180, $max = 180)
*
* @property float[] $localCoordinates
*
* @method float[] localCoordinates()
*
* @property int $randomDigitNotNull
*
* @method int randomDigitNotNull()
*
* @property mixed $passthrough
*
* @method mixed passthrough($value)
*
* @property string $randomLetter
*
* @method string randomLetter()
*
* @property string $randomAscii
*
* @method string randomAscii()
*
* @property array $randomElements
*
* @method array randomElements($array = ['a', 'b', 'c'], $count = 1, $allowDuplicates = false)
*
* @property mixed $randomElement
*
* @method mixed randomElement($array = ['a', 'b', 'c'])
*
* @property int|string|null $randomKey
*
* @method int|string|null randomKey($array = [])
*
* @property array|string $shuffle
*
* @method array|string shuffle($arg = '')
*
* @property array $shuffleArray
*
* @method array shuffleArray($array = [])
*
* @property string $shuffleString
*
* @method string shuffleString($string = '', $encoding = 'UTF-8')
*
* @property string $numerify
*
* @method string numerify($string = '###')
*
* @property string $lexify
*
* @method string lexify($string = '????')
*
* @property string $bothify
*
* @method string bothify($string = '## ??')
*
* @property string $asciify
*
* @method string asciify($string = '****')
*
* @property string $regexify
*
* @method string regexify($regex = '')
*
* @property string $toLower
*
* @method string toLower($string = '')
*
* @property string $toUpper
*
* @method string toUpper($string = '')
*
* @property int $biasedNumberBetween
*
* @method int biasedNumberBetween($min = 0, $max = 100, $function = 'sqrt')
*
* @property string $hexColor
*
* @method string hexColor()
*
* @property string $safeHexColor
*
* @method string safeHexColor()
*
* @property array $rgbColorAsArray
*
* @method array rgbColorAsArray()
*
* @property string $rgbColor
*
* @method string rgbColor()
*
* @property string $rgbCssColor
*
* @method string rgbCssColor()
*
* @property string $rgbaCssColor
*
* @method string rgbaCssColor()
*
* @property string $safeColorName
*
* @method string safeColorName()
*
* @property string $colorName
*
* @method string colorName()
*
* @property string $hslColor
*
* @method string hslColor()
*
* @property array $hslColorAsArray
*
* @method array hslColorAsArray()
*
* @property string $company
*
* @method string company()
*
* @property string $companySuffix
*
* @method string companySuffix()
*
* @property string $jobTitle
*
* @method string jobTitle()
*
* @property int $unixTime
*
* @method int unixTime($max = 'now')
*
* @property \DateTime $dateTime
*
* @method \DateTime dateTime($max = 'now', $timezone = null)
*
* @property \DateTime $dateTimeAD
*
* @method \DateTime dateTimeAD($max = 'now', $timezone = null)
*
* @property string $iso8601
*
* @method string iso8601($max = 'now')
*
* @property string $date
*
* @method string date($format = 'Y-m-d', $max = 'now')
*
* @property string $time
*
* @method string time($format = 'H:i:s', $max = 'now')
*
* @property \DateTime $dateTimeBetween
*
* @method \DateTime dateTimeBetween($startDate = '-30 years', $endDate = 'now', $timezone = null)
*
* @property \DateTime $dateTimeInInterval
*
* @method \DateTime dateTimeInInterval($date = '-30 years', $interval = '+5 days', $timezone = null)
*
* @property \DateTime $dateTimeThisCentury
*
* @method \DateTime dateTimeThisCentury($max = 'now', $timezone = null)
*
* @property \DateTime $dateTimeThisDecade
*
* @method \DateTime dateTimeThisDecade($max = 'now', $timezone = null)
*
* @property \DateTime $dateTimeThisYear
*
* @method \DateTime dateTimeThisYear($max = 'now', $timezone = null)
*
* @property \DateTime $dateTimeThisMonth
*
* @method \DateTime dateTimeThisMonth($max = 'now', $timezone = null)
*
* @property string $amPm
*
* @method string amPm($max = 'now')
*
* @property string $dayOfMonth
*
* @method string dayOfMonth($max = 'now')
*
* @property string $dayOfWeek
*
* @method string dayOfWeek($max = 'now')
*
* @property string $month
*
* @method string month($max = 'now')
*
* @property string $monthName
*
* @method string monthName($max = 'now')
*
* @property string $year
*
* @method string year($max = 'now')
*
* @property string $century
*
* @method string century()
*
* @property string $timezone
*
* @method string timezone($countryCode = null)
*
* @property void $setDefaultTimezone
*
* @method void setDefaultTimezone($timezone = null)
*
* @property string $getDefaultTimezone
*
* @method string getDefaultTimezone()
*
* @property string $file
*
* @method string file($sourceDirectory = '/tmp', $targetDirectory = '/tmp', $fullPath = true)
*
* @property string $randomHtml
*
* @method string randomHtml($maxDepth = 4, $maxWidth = 4)
*
* @property string $imageUrl
*
* @method string imageUrl($width = 640, $height = 480, $category = null, $randomize = true, $word = null, $gray = false, string $format = 'png')
*
* @property string $image
*
* @method string image($dir = null, $width = 640, $height = 480, $category = null, $fullPath = true, $randomize = true, $word = null, $gray = false, string $format = 'png')
*
* @property string $email
*
* @method string email()
*
* @property string $safeEmail
*
* @method string safeEmail()
*
* @property string $freeEmail
*
* @method string freeEmail()
*
* @property string $companyEmail
*
* @method string companyEmail()
*
* @property string $freeEmailDomain
*
* @method string freeEmailDomain()
*
* @property string $safeEmailDomain
*
* @method string safeEmailDomain()
*
* @property string $userName
*
* @method string userName()
*
* @property string $password
*
* @method string password($minLength = 6, $maxLength = 20)
*
* @property string $domainName
*
* @method string domainName()
*
* @property string $domainWord
*
* @method string domainWord()
*
* @property string $tld
*
* @method string tld()
*
* @property string $url
*
* @method string url()
*
* @property string $slug
*
* @method string slug($nbWords = 6, $variableNbWords = true)
*
* @property string $ipv4
*
* @method string ipv4()
*
* @property string $ipv6
*
* @method string ipv6()
*
* @property string $localIpv4
*
* @method string localIpv4()
*
* @property string $macAddress
*
* @method string macAddress()
*
* @property string $word
*
* @method string word()
*
* @property array|string $words
*
* @method array|string words($nb = 3, $asText = false)
*
* @property string $sentence
*
* @method string sentence($nbWords = 6, $variableNbWords = true)
*
* @property array|string $sentences
*
* @method array|string sentences($nb = 3, $asText = false)
*
* @property string $paragraph
*
* @method string paragraph($nbSentences = 3, $variableNbSentences = true)
*
* @property array|string $paragraphs
*
* @method array|string paragraphs($nb = 3, $asText = false)
*
* @property string $text
*
* @method string text($maxNbChars = 200)
*
* @property bool $boolean
*
* @method bool boolean($chanceOfGettingTrue = 50)
*
* @property string $md5
*
* @method string md5()
*
* @property string $sha1
*
* @method string sha1()
*
* @property string $sha256
*
* @method string sha256()
*
* @property string $locale
*
* @method string locale()
*
* @property string $countryCode
*
* @method string countryCode()
*
* @property string $countryISOAlpha3
*
* @method string countryISOAlpha3()
*
* @property string $languageCode
*
* @method string languageCode()
*
* @property string $currencyCode
*
* @method string currencyCode()
*
* @property string $emoji
*
* @method string emoji()
*
* @property string $creditCardType
*
* @method string creditCardType()
*
* @property string $creditCardNumber
*
* @method string creditCardNumber($type = null, $formatted = false, $separator = '-')
*
* @property \DateTime $creditCardExpirationDate
*
* @method \DateTime creditCardExpirationDate($valid = true)
*
* @property string $creditCardExpirationDateString
*
* @method string creditCardExpirationDateString($valid = true, $expirationDateFormat = null)
*
* @property array $creditCardDetails
*
* @method array creditCardDetails($valid = true)
*
* @property string $iban
*
* @method string iban($countryCode = null, $prefix = '', $length = null)
*
* @property string $swiftBicNumber
*
* @method string swiftBicNumber()
*
* @property string $name
*
* @method string name($gender = null)
*
* @property string $firstName
*
* @method string firstName($gender = null)
*
* @property string $firstNameMale
*
* @method string firstNameMale()
*
* @property string $firstNameFemale
*
* @method string firstNameFemale()
*
* @property string $lastName
*
* @method string lastName($gender = null)
*
* @property string $title
*
* @method string title($gender = null)
*
* @property string $titleMale
*
* @method string titleMale()
*
* @property string $titleFemale
*
* @method string titleFemale()
*
* @property string $phoneNumber
*
* @method string phoneNumber()
*
* @property string $e164PhoneNumber
*
* @method string e164PhoneNumber()
*
* @property int $imei
*
* @method int imei()
*
* @property string $realText
*
* @method string realText($maxNbChars = 200, $indexSize = 2)
*
* @property string $realTextBetween
*
* @method string realTextBetween($minNbChars = 160, $maxNbChars = 200, $indexSize = 2)
*
* @property string $macProcessor
*
* @method string macProcessor()
*
* @property string $linuxProcessor
*
* @method string linuxProcessor()
*
* @property string $userAgent
*
* @method string userAgent()
*
* @property string $chrome
*
* @method string chrome()
*
* @property string $msedge
*
* @method string msedge()
*
* @property string $firefox
*
* @method string firefox()
*
* @property string $safari
*
* @method string safari()
*
* @property string $opera
*
* @method string opera()
*
* @property string $internetExplorer
*
* @method string internetExplorer()
*
* @property string $windowsPlatformToken
*
* @method string windowsPlatformToken()
*
* @property string $macPlatformToken
*
* @method string macPlatformToken()
*
* @property string $iosMobileToken
*
* @method string iosMobileToken()
*
* @property string $linuxPlatformToken
*
* @method string linuxPlatformToken()
*
* @property string $uuid
*
* @method string uuid()
*/
class Generator
{
protected $providers = [];
protected $formatters = [];
private $container;
/**
* @var UniqueGenerator
*/
private $uniqueGenerator;
public function __construct(?ContainerInterface $container = null)
{
$this->container = $container ?: Container\ContainerBuilder::withDefaultExtensions()->build();
}
/**
* @template T of Extension\Extension
*
* @param class-string<T> $id
*
* @throws Extension\ExtensionNotFound
*
* @return T
*/
public function ext(string $id): Extension\Extension
{
if (!$this->container->has($id)) {
throw new Extension\ExtensionNotFound(sprintf(
'No Faker extension with id "%s" was loaded.',
$id,
));
}
$extension = $this->container->get($id);
if ($extension instanceof Extension\GeneratorAwareExtension) {
$extension = $extension->withGenerator($this);
}
return $extension;
}
public function addProvider($provider)
{
array_unshift($this->providers, $provider);
$this->formatters = [];
}
public function getProviders()
{
return $this->providers;
}
/**
* With the unique generator you are guaranteed to never get the same two
* values.
*
* <code>
* // will never return twice the same value
* $faker->unique()->randomElement(array(1, 2, 3));
* </code>
*
* @param bool $reset If set to true, resets the list of existing values
* @param int $maxRetries Maximum number of retries to find a unique value,
* After which an OverflowException is thrown.
*
* @throws \OverflowException When no unique value can be found by iterating $maxRetries times
*
* @return self A proxy class returning only non-existing values
*/
public function unique($reset = false, $maxRetries = 10000)
{
if ($reset || $this->uniqueGenerator === null) {
$this->uniqueGenerator = new UniqueGenerator($this, $maxRetries);
}
return $this->uniqueGenerator;
}
/**
* Get a value only some percentage of the time.
*
* @param float $weight A probability between 0 and 1, 0 means that we always get the default value.
*
* @return self
*/
public function optional(float $weight = 0.5, $default = null)
{
if ($weight > 1) {
trigger_deprecation('fakerphp/faker', '1.16', 'First argument ($weight) to method "optional()" must be between 0 and 1. You passed %f, we assume you meant %f.', $weight, $weight / 100);
$weight = $weight / 100;
}
return new ChanceGenerator($this, $weight, $default);
}
/**
* To make sure the value meet some criteria, pass a callable that verifies the
* output. If the validator fails, the generator will try again.
*
* The value validity is determined by a function passed as first argument.
*
* <code>
* $values = array();
* $evenValidator = function ($digit) {
* return $digit % 2 === 0;
* };
* for ($i=0; $i < 10; $i++) {
* $values []= $faker->valid($evenValidator)->randomDigit;
* }
* print_r($values); // [0, 4, 8, 4, 2, 6, 0, 8, 8, 6]
* </code>
*
* @param ?\Closure $validator A function returning true for valid values
* @param int $maxRetries Maximum number of retries to find a valid value,
* After which an OverflowException is thrown.
*
* @throws \OverflowException When no valid value can be found by iterating $maxRetries times
*
* @return self A proxy class returning only valid values
*/
public function valid(?\Closure $validator = null, int $maxRetries = 10000)
{
return new ValidGenerator($this, $validator, $maxRetries);
}
public function seed($seed = null)
{
if ($seed === null) {
mt_srand();
} else {
mt_srand((int) $seed, self::mode());
}
}
/**
* @see https://www.php.net/manual/en/migration83.deprecated.php#migration83.deprecated.random
*/
private static function mode(): int
{
if (PHP_VERSION_ID < 80300) {
return MT_RAND_PHP;
}
return MT_RAND_MT19937;
}
public function format($format, $arguments = [])
{
return call_user_func_array($this->getFormatter($format), $arguments);
}
/**
* @param string $format
*
* @return callable
*/
public function getFormatter($format)
{
if (isset($this->formatters[$format])) {
return $this->formatters[$format];
}
if (method_exists($this, $format)) {
$this->formatters[$format] = [$this, $format];
return $this->formatters[$format];
}
// "Faker\Core\Barcode->ean13"
if (preg_match('|^([a-zA-Z0-9\\\]+)->([a-zA-Z0-9]+)$|', $format, $matches)) {
$this->formatters[$format] = [$this->ext($matches[1]), $matches[2]];
return $this->formatters[$format];
}
foreach ($this->providers as $provider) {
if (method_exists($provider, $format)) {
$this->formatters[$format] = [$provider, $format];
return $this->formatters[$format];
}
}
throw new \InvalidArgumentException(sprintf('Unknown format "%s"', $format));
}
/**
* Replaces tokens ('{{ tokenName }}') with the result from the token method call
*
* @param string $string String that needs to bet parsed
*
* @return string
*/
public function parse($string)
{
$callback = function ($matches) {
return $this->format($matches[1]);
};
return preg_replace_callback('/{{\s?(\w+|[\w\\\]+->\w+?)\s?}}/u', $callback, $string);
}
/**
* Get a random MIME type
*
* @example 'video/avi'
*/
public function mimeType()
{
return $this->ext(Extension\FileExtension::class)->mimeType();
}
/**
* Get a random file extension (without a dot)
*
* @example avi
*/
public function fileExtension()
{
return $this->ext(Extension\FileExtension::class)->extension();
}
/**
* Get a full path to a new real file on the system.
*/
public function filePath()
{
return $this->ext(Extension\FileExtension::class)->filePath();
}
/**
* Get an actual blood type
*
* @example 'AB'
*/
public function bloodType(): string
{
return $this->ext(Extension\BloodExtension::class)->bloodType();
}
/**
* Get a random resis value
*
* @example '+'
*/
public function bloodRh(): string
{
return $this->ext(Extension\BloodExtension::class)->bloodRh();
}
/**
* Get a full blood group
*
* @example 'AB+'
*/
public function bloodGroup(): string
{
return $this->ext(Extension\BloodExtension::class)->bloodGroup();
}
/**
* Get a random EAN13 barcode.
*
* @example '4006381333931'
*/
public function ean13(): string
{
return $this->ext(Extension\BarcodeExtension::class)->ean13();
}
/**
* Get a random EAN8 barcode.
*
* @example '73513537'
*/
public function ean8(): string
{
return $this->ext(Extension\BarcodeExtension::class)->ean8();
}
/**
* Get a random ISBN-10 code
*
* @see http://en.wikipedia.org/wiki/International_Standard_Book_Number
*
* @example '4881416324'
*/
public function isbn10(): string
{
return $this->ext(Extension\BarcodeExtension::class)->isbn10();
}
/**
* Get a random ISBN-13 code
*
* @see http://en.wikipedia.org/wiki/International_Standard_Book_Number
*
* @example '9790404436093'
*/
public function isbn13(): string
{
return $this->ext(Extension\BarcodeExtension::class)->isbn13();
}
/**
* Returns a random number between $int1 and $int2 (any order)
*
* @example 79907610
*/
public function numberBetween($int1 = 0, $int2 = 2147483647): int
{
return $this->ext(Extension\NumberExtension::class)->numberBetween((int) $int1, (int) $int2);
}
/**
* Returns a random number between 0 and 9
*/
public function randomDigit(): int
{
return $this->ext(Extension\NumberExtension::class)->randomDigit();
}
/**
* Generates a random digit, which cannot be $except
*/
public function randomDigitNot($except): int
{
return $this->ext(Extension\NumberExtension::class)->randomDigitNot((int) $except);
}
/**
* Returns a random number between 1 and 9
*/
public function randomDigitNotZero(): int
{
return $this->ext(Extension\NumberExtension::class)->randomDigitNotZero();
}
/**
* Return a random float number
*
* @example 48.8932
*/
public function randomFloat($nbMaxDecimals = null, $min = 0, $max = null): float
{
return $this->ext(Extension\NumberExtension::class)->randomFloat(
$nbMaxDecimals !== null ? (int) $nbMaxDecimals : null,
(float) $min,
$max !== null ? (float) $max : null,
);
}
/**
* Returns a random integer with 0 to $nbDigits digits.
*
* The maximum value returned is mt_getrandmax()
*
* @param int|null $nbDigits Defaults to a random number between 1 and 9
* @param bool $strict Whether the returned number should have exactly $nbDigits
*
* @example 79907610
*/
public function randomNumber($nbDigits = null, $strict = false): int
{
return $this->ext(Extension\NumberExtension::class)->randomNumber(
$nbDigits !== null ? (int) $nbDigits : null,
(bool) $strict,
);
}
/**
* Get a version number in semantic versioning syntax 2.0.0. (https://semver.org/spec/v2.0.0.html)
*
* @param bool $preRelease Pre release parts may be randomly included
* @param bool $build Build parts may be randomly included
*
* @example 1.0.0
* @example 1.0.0-alpha.1
* @example 1.0.0-alpha.1+b71f04d
*/
public function semver(bool $preRelease = false, bool $build = false): string
{
return $this->ext(Extension\VersionExtension::class)->semver($preRelease, $build);
}
/**
* @deprecated
*/
protected function callFormatWithMatches($matches)
{
trigger_deprecation('fakerphp/faker', '1.14', 'Protected method "callFormatWithMatches()" is deprecated and will be removed.');
return $this->format($matches[1]);
}
/**
* @param string $attribute
*
* @deprecated Use a method instead.
*/
public function __get($attribute)
{
trigger_deprecation('fakerphp/faker', '1.14', 'Accessing property "%s" is deprecated, use "%s()" instead.', $attribute, $attribute);
return $this->format($attribute);
}
/**
* @param string $method
* @param array $attributes
*/
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
public function __destruct()
{
$this->seed();
}
public function __wakeup()
{
$this->formatters = [];
}
}
+180
View File
@@ -0,0 +1,180 @@
<?php
namespace Faker\Guesser;
use Faker\Provider\Base;
class Name
{
protected $generator;
public function __construct(\Faker\Generator $generator)
{
$this->generator = $generator;
}
/**
* @param string $name
* @param int|null $size Length of field, if known
*
* @return callable|null
*/
public function guessFormat($name, $size = null)
{
$name = Base::toLower($name);
$generator = $this->generator;
if (preg_match('/^is[_A-Z]/', $name)) {
return static function () use ($generator) {
return $generator->boolean();
};
}
if (preg_match('/(_a|A)t$/', $name)) {
return static function () use ($generator) {
return $generator->dateTime();
};
}
switch (str_replace('_', '', $name)) {
case 'firstname':
return static function () use ($generator) {
return $generator->firstName();
};
case 'lastname':
return static function () use ($generator) {
return $generator->lastName();
};
case 'username':
case 'login':
return static function () use ($generator) {
return $generator->userName();
};
case 'email':
case 'emailaddress':
return static function () use ($generator) {
return $generator->email();
};
case 'phonenumber':
case 'phone':
case 'telephone':
case 'telnumber':
return static function () use ($generator) {
return $generator->phoneNumber();
};
case 'address':
return static function () use ($generator) {
return $generator->address();
};
case 'city':
case 'town':
return static function () use ($generator) {
return $generator->city();
};
case 'streetaddress':
return static function () use ($generator) {
return $generator->streetAddress();
};
case 'postcode':
case 'zipcode':
return static function () use ($generator) {
return $generator->postcode();
};
case 'state':
return static function () use ($generator) {
return $generator->state();
};
case 'county':
if ($this->generator->locale == 'en_US') {
return static function () use ($generator) {
return sprintf('%s County', $generator->city());
};
}
return static function () use ($generator) {
return $generator->state();
};
case 'country':
switch ($size) {
case 2:
return static function () use ($generator) {
return $generator->countryCode();
};
case 3:
return static function () use ($generator) {
return $generator->countryISOAlpha3();
};
case 5:
case 6:
return static function () use ($generator) {
return $generator->locale();
};
default:
return static function () use ($generator) {
return $generator->country();
};
}
break;
case 'locale':
return static function () use ($generator) {
return $generator->locale();
};
case 'currency':
case 'currencycode':
return static function () use ($generator) {
return $generator->currencyCode();
};
case 'url':
case 'website':
return static function () use ($generator) {
return $generator->url();
};
case 'company':
case 'companyname':
case 'employer':
return static function () use ($generator) {
return $generator->company();
};
case 'title':
if ($size !== null && $size <= 10) {
return static function () use ($generator) {
return $generator->title();
};
}
return static function () use ($generator) {
return $generator->sentence();
};
case 'body':
case 'summary':
case 'article':
case 'description':
return static function () use ($generator) {
return $generator->text();
};
}
return null;
}
}
@@ -0,0 +1,79 @@
<?php
namespace Faker\ORM\CakePHP;
class ColumnTypeGuesser
{
protected $generator;
public function __construct(\Faker\Generator $generator)
{
$this->generator = $generator;
}
/**
* @return \Closure|null
*/
public function guessFormat($column, $table)
{
$generator = $this->generator;
$schema = $table->schema();
switch ($schema->columnType($column)) {
case 'boolean':
return static function () use ($generator) {
return $generator->boolean;
};
case 'integer':
return static function () use ($generator) {
return $generator->numberBetween(0, 2147483647);
};
case 'biginteger':
return static function () use ($generator) {
return $generator->numberBetween(0, PHP_INT_MAX);
};
case 'decimal':
case 'float':
return static function () use ($generator) {
return $generator->randomFloat();
};
case 'uuid':
return static function () use ($generator) {
return $generator->uuid();
};
case 'string':
if (method_exists($schema, 'getColumn')) {
$columnData = $schema->getColumn($column);
} else {
$columnData = $schema->column($column);
}
$length = $columnData['length'];
return static function () use ($generator, $length) {
return $generator->text($length);
};
case 'text':
return static function () use ($generator) {
return $generator->text();
};
case 'date':
case 'datetime':
case 'timestamp':
case 'time':
return static function () use ($generator) {
return $generator->datetime();
};
case 'binary':
default:
return null;
}
}
}
@@ -0,0 +1,173 @@
<?php
namespace Faker\ORM\CakePHP;
use Cake\ORM\TableRegistry;
class EntityPopulator
{
protected $class;
protected $connectionName;
protected $columnFormatters = [];
protected $modifiers = [];
public function __construct($class)
{
$this->class = $class;
}
/**
* @param string $name
*/
public function __get($name)
{
return $this->{$name};
}
/**
* @param string $name
*/
public function __set($name, $value)
{
$this->{$name} = $value;
}
public function mergeColumnFormattersWith($columnFormatters)
{
$this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters);
}
public function mergeModifiersWith($modifiers)
{
$this->modifiers = array_merge($this->modifiers, $modifiers);
}
/**
* @return array
*/
public function guessColumnFormatters($populator)
{
$formatters = [];
$class = $this->class;
$table = $this->getTable($class);
$schema = $table->schema();
$pk = $schema->primaryKey();
$guessers = $populator->getGuessers() + ['ColumnTypeGuesser' => new ColumnTypeGuesser($populator->getGenerator())];
$isForeignKey = static function ($column) use ($table) {
foreach ($table->associations()->type('BelongsTo') as $assoc) {
if ($column == $assoc->foreignKey()) {
return true;
}
}
return false;
};
foreach ($schema->columns() as $column) {
if ($column == $pk[0] || $isForeignKey($column)) {
continue;
}
foreach ($guessers as $guesser) {
if ($formatter = $guesser->guessFormat($column, $table)) {
$formatters[$column] = $formatter;
break;
}
}
}
return $formatters;
}
/**
* @return array
*/
public function guessModifiers()
{
$modifiers = [];
$table = $this->getTable($this->class);
$belongsTo = $table->associations()->type('BelongsTo');
foreach ($belongsTo as $assoc) {
$modifiers['belongsTo' . $assoc->name()] = function ($data, $insertedEntities) use ($assoc) {
$table = $assoc->target();
$foreignModel = $table->alias();
$foreignKeys = [];
if (!empty($insertedEntities[$foreignModel])) {
$foreignKeys = $insertedEntities[$foreignModel];
} else {
$foreignKeys = $table->find('all')
->select(['id'])
->map(static function ($row) {
return $row->id;
})
->toArray();
}
if (empty($foreignKeys)) {
throw new \Exception(sprintf('%s belongsTo %s, which seems empty at this point.', $this->getTable($this->class)->table(), $assoc->table()));
}
$foreignKey = $foreignKeys[array_rand($foreignKeys)];
$data[$assoc->foreignKey()] = $foreignKey;
return $data;
};
}
// TODO check if TreeBehavior attached to modify lft/rgt cols
return $modifiers;
}
/**
* @param array $options
*/
public function execute($class, $insertedEntities, $options = [])
{
$table = $this->getTable($class);
$entity = $table->newEntity();
foreach ($this->columnFormatters as $column => $format) {
if (null !== $format) {
$entity->{$column} = is_callable($format) ? $format($insertedEntities, $table) : $format;
}
}
foreach ($this->modifiers as $modifier) {
$entity = $modifier($entity, $insertedEntities);
}
if (!$entity = $table->save($entity, $options)) {
throw new \RuntimeException("Failed saving $class record");
}
$pk = $table->primaryKey();
if (is_string($pk)) {
return $entity->{$pk};
}
return $entity->{$pk[0]};
}
public function setConnection($name)
{
$this->connectionName = $name;
}
protected function getTable($class)
{
$options = [];
if (!empty($this->connectionName)) {
$options['connection'] = $this->connectionName;
}
return TableRegistry::get($class, $options);
}
}
@@ -0,0 +1,113 @@
<?php
namespace Faker\ORM\CakePHP;
class Populator
{
protected $generator;
protected $entities = [];
protected $quantities = [];
protected $guessers = [];
public function __construct(\Faker\Generator $generator)
{
$this->generator = $generator;
}
/**
* @return \Faker\Generator
*/
public function getGenerator()
{
return $this->generator;
}
/**
* @return array
*/
public function getGuessers()
{
return $this->guessers;
}
/**
* @return $this
*/
public function removeGuesser($name)
{
if ($this->guessers[$name]) {
unset($this->guessers[$name]);
}
return $this;
}
/**
* @throws \Exception
*
* @return $this
*/
public function addGuesser($class)
{
if (!is_object($class)) {
$class = new $class($this->generator);
}
if (!method_exists($class, 'guessFormat')) {
throw new \Exception('Missing required custom guesser method: ' . get_class($class) . '::guessFormat()');
}
$this->guessers[get_class($class)] = $class;
return $this;
}
/**
* @param array $customColumnFormatters
* @param array $customModifiers
*
* @return $this
*/
public function addEntity($entity, $number, $customColumnFormatters = [], $customModifiers = [])
{
if (!$entity instanceof EntityPopulator) {
$entity = new EntityPopulator($entity);
}
$entity->columnFormatters = $entity->guessColumnFormatters($this);
if ($customColumnFormatters) {
$entity->mergeColumnFormattersWith($customColumnFormatters);
}
$entity->modifiers = $entity->guessModifiers($this);
if ($customModifiers) {
$entity->mergeModifiersWith($customModifiers);
}
$class = $entity->class;
$this->entities[$class] = $entity;
$this->quantities[$class] = $number;
return $this;
}
/**
* @param array $options
*
* @return array
*/
public function execute($options = [])
{
$insertedEntities = [];
foreach ($this->quantities as $class => $number) {
for ($i = 0; $i < $number; ++$i) {
$insertedEntities[$class][] = $this->entities[$class]->execute($class, $insertedEntities, $options);
}
}
return $insertedEntities;
}
}
@@ -0,0 +1,91 @@
<?php
namespace Faker\ORM\Doctrine;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Faker\Generator;
require_once 'backward-compatibility.php';
class ColumnTypeGuesser
{
protected $generator;
public function __construct(Generator $generator)
{
$this->generator = $generator;
}
/**
* @return \Closure|null
*/
public function guessFormat($fieldName, ClassMetadata $class)
{
$generator = $this->generator;
$type = $class->getTypeOfField($fieldName);
switch ($type) {
case 'boolean':
return static function () use ($generator) {
return $generator->boolean();
};
case 'decimal':
$size = $class->fieldMappings[$fieldName]['precision'] ?? 2;
return static function () use ($generator, $size) {
return $generator->randomNumber($size + 2) / 100;
};
case 'smallint':
return static function () use ($generator) {
return $generator->numberBetween(0, 65535);
};
case 'integer':
return static function () use ($generator) {
return $generator->numberBetween(0, 2147483647);
};
case 'bigint':
return static function () use ($generator) {
return $generator->numberBetween(0, PHP_INT_MAX);
};
case 'float':
return static function () use ($generator) {
return $generator->randomFloat();
};
case 'string':
$size = $class->fieldMappings[$fieldName]['length'] ?? 255;
return static function () use ($generator, $size) {
return $generator->text($size);
};
case 'text':
return static function () use ($generator) {
return $generator->text();
};
case 'datetime':
case 'date':
case 'time':
return static function () use ($generator) {
return $generator->datetime();
};
case 'datetime_immutable':
case 'date_immutable':
case 'time_immutable':
return static function () use ($generator) {
return \DateTimeImmutable::createFromMutable($generator->datetime);
};
default:
// no smart way to guess what the user expects here
return null;
}
}
}
@@ -0,0 +1,248 @@
<?php
namespace Faker\ORM\Doctrine;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\ObjectManager;
require_once 'backward-compatibility.php';
/**
* Service class for populating a table through a Doctrine Entity class.
*/
class EntityPopulator
{
/**
* @var ClassMetadata
*/
protected $class;
/**
* @var array
*/
protected $columnFormatters = [];
/**
* @var array
*/
protected $modifiers = [];
public function __construct(ClassMetadata $class)
{
$this->class = $class;
}
/**
* @return string
*/
public function getClass()
{
return $this->class->getName();
}
public function setColumnFormatters($columnFormatters)
{
$this->columnFormatters = $columnFormatters;
}
/**
* @return array
*/
public function getColumnFormatters()
{
return $this->columnFormatters;
}
public function mergeColumnFormattersWith($columnFormatters)
{
$this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters);
}
public function setModifiers(array $modifiers)
{
$this->modifiers = $modifiers;
}
/**
* @return array
*/
public function getModifiers()
{
return $this->modifiers;
}
public function mergeModifiersWith(array $modifiers)
{
$this->modifiers = array_merge($this->modifiers, $modifiers);
}
/**
* @return array
*/
public function guessColumnFormatters(\Faker\Generator $generator)
{
$formatters = [];
$nameGuesser = new \Faker\Guesser\Name($generator);
$columnTypeGuesser = new ColumnTypeGuesser($generator);
foreach ($this->class->getFieldNames() as $fieldName) {
if ($this->class->isIdentifier($fieldName) || !$this->class->hasField($fieldName)) {
continue;
}
$size = $this->class->fieldMappings[$fieldName]['length'] ?? null;
if ($formatter = $nameGuesser->guessFormat($fieldName, $size)) {
$formatters[$fieldName] = $formatter;
continue;
}
if ($formatter = $columnTypeGuesser->guessFormat($fieldName, $this->class)) {
$formatters[$fieldName] = $formatter;
continue;
}
}
foreach ($this->class->getAssociationNames() as $assocName) {
if ($this->class->isCollectionValuedAssociation($assocName)) {
continue;
}
$relatedClass = $this->class->getAssociationTargetClass($assocName);
$unique = $optional = false;
if ($this->class instanceof \Doctrine\ORM\Mapping\ClassMetadata) {
$mappings = $this->class->getAssociationMappings();
foreach ($mappings as $mapping) {
if ($mapping['targetEntity'] == $relatedClass) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_ONE) {
$unique = true;
$optional = $mapping['joinColumns'][0]['nullable'] ?? false;
break;
}
}
}
} elseif ($this->class instanceof \Doctrine\ODM\MongoDB\Mapping\ClassMetadata) {
$mappings = $this->class->associationMappings;
foreach ($mappings as $mapping) {
if ($mapping['targetDocument'] == $relatedClass) {
if ($mapping['type'] == \Doctrine\ODM\MongoDB\Mapping\ClassMetadata::ONE && $mapping['association'] == \Doctrine\ODM\MongoDB\Mapping\ClassMetadata::REFERENCE_ONE) {
$unique = true;
$optional = $mapping['nullable'] ?? false;
break;
}
}
}
}
$index = 0;
$formatters[$assocName] = static function ($inserted) use ($relatedClass, &$index, $unique, $optional, $generator) {
if (isset($inserted[$relatedClass])) {
if ($unique) {
$related = null;
if (isset($inserted[$relatedClass][$index]) || !$optional) {
$related = $inserted[$relatedClass][$index];
}
++$index;
return $related;
}
return $generator->randomElement($inserted[$relatedClass]);
}
return null;
};
}
return $formatters;
}
/**
* Insert one new record using the Entity class.
*
* @param bool $generateId
*
* @return EntityPopulator
*/
public function execute(ObjectManager $manager, $insertedEntities, $generateId = false)
{
$obj = $this->class->newInstance();
$this->fillColumns($obj, $insertedEntities);
$this->callMethods($obj, $insertedEntities);
if ($generateId) {
$idsName = $this->class->getIdentifier();
foreach ($idsName as $idName) {
$id = $this->generateId($obj, $idName, $manager);
$this->class->reflFields[$idName]->setValue($obj, $id);
}
}
$manager->persist($obj);
return $obj;
}
private function fillColumns($obj, $insertedEntities): void
{
foreach ($this->columnFormatters as $field => $format) {
if (null !== $format) {
// Add some extended debugging information to any errors thrown by the formatter
try {
$value = is_callable($format) ? $format($insertedEntities, $obj) : $format;
} catch (\InvalidArgumentException $ex) {
throw new \InvalidArgumentException(sprintf(
'Failed to generate a value for %s::%s: %s',
get_class($obj),
$field,
$ex->getMessage(),
));
}
// Try a standard setter if it's available, otherwise fall back on reflection
$setter = sprintf('set%s', ucfirst($field));
if (is_callable([$obj, $setter])) {
$obj->$setter($value);
} else {
$this->class->reflFields[$field]->setValue($obj, $value);
}
}
}
}
private function callMethods($obj, $insertedEntities): void
{
foreach ($this->getModifiers() as $modifier) {
$modifier($obj, $insertedEntities);
}
}
/**
* @return int
*/
private function generateId($obj, $column, ObjectManager $manager)
{
$repository = $manager->getRepository(get_class($obj));
$result = $repository->createQueryBuilder('e')
->select(sprintf('e.%s', $column))
->getQuery()
->execute();
$ids = array_map('current', $result->toArray());
do {
$id = mt_rand();
} while (in_array($id, $ids, false));
return $id;
}
}
@@ -0,0 +1,126 @@
<?php
namespace Faker\ORM\Doctrine;
use Doctrine\Common\Persistence\ObjectManager;
use Faker\Generator;
require_once 'backward-compatibility.php';
/**
* Service class for populating a database using the Doctrine ORM or ODM.
* A Populator can populate several tables using ActiveRecord classes.
*/
class Populator
{
/**
* @var int
*/
protected $batchSize;
/**
* @var Generator
*/
protected $generator;
/**
* @var ObjectManager|null
*/
protected $manager;
/**
* @var array
*/
protected $entities = [];
/**
* @var array
*/
protected $quantities = [];
/**
* @var array
*/
protected $generateId = [];
/**
* Populator constructor.
*
* @param int $batchSize
*/
public function __construct(Generator $generator, ?ObjectManager $manager = null, $batchSize = 1000)
{
$this->generator = $generator;
$this->manager = $manager;
$this->batchSize = $batchSize;
}
/**
* Add an order for the generation of $number records for $entity.
*
* @param mixed $entity A Doctrine classname, or a \Faker\ORM\Doctrine\EntityPopulator instance
* @param int $number The number of entities to populate
*/
public function addEntity($entity, $number, $customColumnFormatters = [], $customModifiers = [], $generateId = false)
{
if (!$entity instanceof \Faker\ORM\Doctrine\EntityPopulator) {
if (null === $this->manager) {
throw new \InvalidArgumentException('No entity manager passed to Doctrine Populator.');
}
$entity = new \Faker\ORM\Doctrine\EntityPopulator($this->manager->getClassMetadata($entity));
}
$entity->setColumnFormatters($entity->guessColumnFormatters($this->generator));
if ($customColumnFormatters) {
$entity->mergeColumnFormattersWith($customColumnFormatters);
}
$entity->mergeModifiersWith($customModifiers);
$this->generateId[$entity->getClass()] = $generateId;
$class = $entity->getClass();
$this->entities[$class] = $entity;
$this->quantities[$class] = $number;
}
/**
* Populate the database using all the Entity classes previously added.
*
* Please note that large amounts of data will result in more memory usage since the the Populator will return
* all newly created primary keys after executing.
*
* @param ObjectManager|null $entityManager A Doctrine connection object
*
* @return array A list of the inserted PKs
*/
public function execute($entityManager = null)
{
if (null === $entityManager) {
$entityManager = $this->manager;
}
if (null === $entityManager) {
throw new \InvalidArgumentException('No entity manager passed to Doctrine Populator.');
}
$insertedEntities = [];
foreach ($this->quantities as $class => $number) {
$generateId = $this->generateId[$class];
for ($i = 0; $i < $number; ++$i) {
$insertedEntities[$class][] = $this->entities[$class]->execute(
$entityManager,
$insertedEntities,
$generateId,
);
if (count($insertedEntities) % $this->batchSize === 0) {
$entityManager->flush();
}
}
$entityManager->flush();
}
return $insertedEntities;
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
if (!class_exists('Doctrine\Common\Persistence\Mapping\ClassMetadata')) {
class_alias(\Doctrine\Persistence\Mapping\ClassMetadata::class, 'Doctrine\Common\Persistence\Mapping\ClassMetadata');
}
if (!class_exists('Doctrine\Common\Persistence\ObjectManager')) {
class_alias(\Doctrine\Persistence\ObjectManager::class, 'Doctrine\Common\Persistence\ObjectManager');
}
@@ -0,0 +1,57 @@
<?php
namespace Faker\ORM\Mandango;
use Faker\Generator;
class ColumnTypeGuesser
{
/**
* @var Generator
*/
protected $generator;
public function __construct(Generator $generator)
{
$this->generator = $generator;
}
/**
* @return \Closure|null
*/
public function guessFormat($field)
{
$generator = $this->generator;
switch ($field['type']) {
case 'boolean':
return static function () use ($generator) {
return $generator->boolean;
};
case 'integer':
return static function () use ($generator) {
return $generator->numberBetween(0, 4294967295);
};
case 'float':
return static function () use ($generator) {
return $generator->randomFloat();
};
case 'string':
return static function () use ($generator) {
return $generator->text(255);
};
case 'date':
return static function () use ($generator) {
return $generator->dateTime;
};
default:
// no smart way to guess what the user expects here
return null;
}
}
}
@@ -0,0 +1,123 @@
<?php
namespace Faker\ORM\Mandango;
use Faker\Provider\Base;
use Mandango\Mandango;
/**
* Service class for populating a table through a Mandango ActiveRecord class.
*/
class EntityPopulator
{
protected $class;
protected $columnFormatters = [];
/**
* @param string $class A Mandango ActiveRecord classname
*/
public function __construct($class)
{
$this->class = $class;
}
/**
* @return string
*/
public function getClass()
{
return $this->class;
}
public function setColumnFormatters($columnFormatters)
{
$this->columnFormatters = $columnFormatters;
}
/**
* @return array
*/
public function getColumnFormatters()
{
return $this->columnFormatters;
}
public function mergeColumnFormattersWith($columnFormatters)
{
$this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters);
}
/**
* @return array
*/
public function guessColumnFormatters(\Faker\Generator $generator, Mandango $mandango)
{
$formatters = [];
$nameGuesser = new \Faker\Guesser\Name($generator);
$columnTypeGuesser = new \Faker\ORM\Mandango\ColumnTypeGuesser($generator);
$metadata = $mandango->getMetadata($this->class);
// fields
foreach ($metadata['fields'] as $fieldName => $field) {
if ($formatter = $nameGuesser->guessFormat($fieldName)) {
$formatters[$fieldName] = $formatter;
continue;
}
if ($formatter = $columnTypeGuesser->guessFormat($field)) {
$formatters[$fieldName] = $formatter;
continue;
}
}
// references
foreach (array_merge($metadata['referencesOne'], $metadata['referencesMany']) as $referenceName => $reference) {
if (!isset($reference['class'])) {
continue;
}
$referenceClass = $reference['class'];
$formatters[$referenceName] = static function ($insertedEntities) use ($referenceClass) {
if (isset($insertedEntities[$referenceClass])) {
return Base::randomElement($insertedEntities[$referenceClass]);
}
return null;
};
}
return $formatters;
}
/**
* Insert one new record using the Entity class.
*/
public function execute(Mandango $mandango, $insertedEntities)
{
$metadata = $mandango->getMetadata($this->class);
$obj = $mandango->create($this->class);
foreach ($this->columnFormatters as $column => $format) {
if (null !== $format) {
$value = is_callable($format) ? $format($insertedEntities, $obj) : $format;
if (isset($metadata['fields'][$column])
|| isset($metadata['referencesOne'][$column])) {
$obj->set($column, $value);
}
if (isset($metadata['referencesMany'][$column])) {
$adder = 'add' . ucfirst($column);
$obj->$adder($value);
}
}
}
$mandango->persist($obj);
return $obj;
}
}
@@ -0,0 +1,63 @@
<?php
namespace Faker\ORM\Mandango;
use Mandango\Mandango;
/**
* Service class for populating a database using Mandango.
* A Populator can populate several tables using ActiveRecord classes.
*/
class Populator
{
protected $generator;
protected $mandango;
protected $entities = [];
protected $quantities = [];
public function __construct(\Faker\Generator $generator, Mandango $mandango)
{
$this->generator = $generator;
$this->mandango = $mandango;
}
/**
* Add an order for the generation of $number records for $entity.
*
* @param mixed $entity A Propel ActiveRecord classname, or a \Faker\ORM\Propel\EntityPopulator instance
* @param int $number The number of entities to populate
*/
public function addEntity($entity, $number, $customColumnFormatters = [])
{
if (!$entity instanceof \Faker\ORM\Mandango\EntityPopulator) {
$entity = new \Faker\ORM\Mandango\EntityPopulator($entity);
}
$entity->setColumnFormatters($entity->guessColumnFormatters($this->generator, $this->mandango));
if ($customColumnFormatters) {
$entity->mergeColumnFormattersWith($customColumnFormatters);
}
$class = $entity->getClass();
$this->entities[$class] = $entity;
$this->quantities[$class] = $number;
}
/**
* Populate the database using all the Entity classes previously added.
*
* @return array A list of the inserted entities.
*/
public function execute()
{
$insertedEntities = [];
foreach ($this->quantities as $class => $number) {
for ($i = 0; $i < $number; ++$i) {
$insertedEntities[$class][] = $this->entities[$class]->execute($this->mandango, $insertedEntities);
}
}
$this->mandango->flush();
return $insertedEntities;
}
}
@@ -0,0 +1,109 @@
<?php
namespace Faker\ORM\Propel;
class ColumnTypeGuesser
{
protected $generator;
public function __construct(\Faker\Generator $generator)
{
$this->generator = $generator;
}
/**
* @return \Closure|null
*/
public function guessFormat(\ColumnMap $column)
{
$generator = $this->generator;
if ($column->isTemporal()) {
if ($column->isEpochTemporal()) {
return static function () use ($generator) {
return $generator->dateTime;
};
}
return static function () use ($generator) {
return $generator->dateTimeAD;
};
}
$type = $column->getType();
switch ($type) {
case \PropelColumnTypes::BOOLEAN:
case \PropelColumnTypes::BOOLEAN_EMU:
return static function () use ($generator) {
return $generator->boolean;
};
case \PropelColumnTypes::NUMERIC:
case \PropelColumnTypes::DECIMAL:
$size = $column->getSize();
return static function () use ($generator, $size) {
return $generator->randomNumber($size + 2) / 100;
};
case \PropelColumnTypes::TINYINT:
return static function () use ($generator) {
return $generator->numberBetween(0, 127);
};
case \PropelColumnTypes::SMALLINT:
return static function () use ($generator) {
return $generator->numberBetween(0, 32767);
};
case \PropelColumnTypes::INTEGER:
return static function () use ($generator) {
return $generator->numberBetween(0, 2147483647);
};
case \PropelColumnTypes::BIGINT:
return static function () use ($generator) {
return $generator->numberBetween(0, PHP_INT_MAX);
};
case \PropelColumnTypes::FLOAT:
case \PropelColumnTypes::DOUBLE:
case \PropelColumnTypes::REAL:
return static function () use ($generator) {
return $generator->randomFloat();
};
case \PropelColumnTypes::CHAR:
case \PropelColumnTypes::VARCHAR:
case \PropelColumnTypes::BINARY:
case \PropelColumnTypes::VARBINARY:
$size = $column->getSize();
return static function () use ($generator, $size) {
return $generator->text($size);
};
case \PropelColumnTypes::LONGVARCHAR:
case \PropelColumnTypes::LONGVARBINARY:
case \PropelColumnTypes::CLOB:
case \PropelColumnTypes::CLOB_EMU:
case \PropelColumnTypes::BLOB:
return static function () use ($generator) {
return $generator->text;
};
case \PropelColumnTypes::ENUM:
$valueSet = $column->getValueSet();
return static function () use ($generator, $valueSet) {
return $generator->randomElement($valueSet);
};
case \PropelColumnTypes::OBJECT:
case \PropelColumnTypes::PHP_ARRAY:
default:
// no smart way to guess what the user expects here
return null;
}
}
}
@@ -0,0 +1,204 @@
<?php
namespace Faker\ORM\Propel;
use Faker\Provider\Base;
/**
* Service class for populating a table through a Propel ActiveRecord class.
*/
class EntityPopulator
{
protected $class;
protected $columnFormatters = [];
protected $modifiers = [];
/**
* @param string $class A Propel ActiveRecord classname
*/
public function __construct($class)
{
$this->class = $class;
}
/**
* @return string
*/
public function getClass()
{
return $this->class;
}
public function setColumnFormatters($columnFormatters)
{
$this->columnFormatters = $columnFormatters;
}
/**
* @return array
*/
public function getColumnFormatters()
{
return $this->columnFormatters;
}
public function mergeColumnFormattersWith($columnFormatters)
{
$this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters);
}
/**
* @return array
*/
public function guessColumnFormatters(\Faker\Generator $generator)
{
$formatters = [];
$class = $this->class;
$peerClass = $class::PEER;
$tableMap = $peerClass::getTableMap();
$nameGuesser = new \Faker\Guesser\Name($generator);
$columnTypeGuesser = new \Faker\ORM\Propel\ColumnTypeGuesser($generator);
foreach ($tableMap->getColumns() as $columnMap) {
// skip behavior columns, handled by modifiers
if ($this->isColumnBehavior($columnMap)) {
continue;
}
if ($columnMap->isForeignKey()) {
$relatedClass = $columnMap->getRelation()->getForeignTable()->getClassname();
$formatters[$columnMap->getPhpName()] = static function ($inserted) use ($relatedClass, $generator) {
return isset($inserted[$relatedClass]) ? $generator->randomElement($inserted[$relatedClass]) : null;
};
continue;
}
if ($columnMap->isPrimaryKey()) {
continue;
}
if ($formatter = $nameGuesser->guessFormat($columnMap->getPhpName(), $columnMap->getSize())) {
$formatters[$columnMap->getPhpName()] = $formatter;
continue;
}
if ($formatter = $columnTypeGuesser->guessFormat($columnMap)) {
$formatters[$columnMap->getPhpName()] = $formatter;
continue;
}
}
return $formatters;
}
/**
* @return bool
*/
protected function isColumnBehavior(\ColumnMap $columnMap)
{
foreach ($columnMap->getTable()->getBehaviors() as $name => $params) {
$columnName = Base::toLower($columnMap->getName());
switch ($name) {
case 'nested_set':
$columnNames = [$params['left_column'], $params['right_column'], $params['level_column']];
if (in_array($columnName, $columnNames, false)) {
return true;
}
break;
case 'timestampable':
$columnNames = [$params['create_column'], $params['update_column']];
if (in_array($columnName, $columnNames, false)) {
return true;
}
break;
}
}
return false;
}
public function setModifiers($modifiers)
{
$this->modifiers = $modifiers;
}
/**
* @return array
*/
public function getModifiers()
{
return $this->modifiers;
}
public function mergeModifiersWith($modifiers)
{
$this->modifiers = array_merge($this->modifiers, $modifiers);
}
/**
* @return array
*/
public function guessModifiers(\Faker\Generator $generator)
{
$modifiers = [];
$class = $this->class;
$peerClass = $class::PEER;
$tableMap = $peerClass::getTableMap();
foreach ($tableMap->getBehaviors() as $name => $params) {
switch ($name) {
case 'nested_set':
$modifiers['nested_set'] = static function ($obj, $inserted) use ($class, $generator): void {
if (isset($inserted[$class])) {
$queryClass = $class . 'Query';
$parent = $queryClass::create()->findPk($generator->randomElement($inserted[$class]));
$obj->insertAsLastChildOf($parent);
} else {
$obj->makeRoot();
}
};
break;
case 'sortable':
$modifiers['sortable'] = static function ($obj, $inserted) use ($class, $generator): void {
$obj->insertAtRank($generator->numberBetween(1, count($inserted[$class] ?? []) + 1));
};
break;
}
}
return $modifiers;
}
/**
* Insert one new record using the Entity class.
*/
public function execute($con, $insertedEntities)
{
$obj = new $this->class();
foreach ($this->getColumnFormatters() as $column => $format) {
if (null !== $format) {
$obj->setByName($column, is_callable($format) ? $format($insertedEntities, $obj) : $format);
}
}
foreach ($this->getModifiers() as $modifier) {
$modifier($obj, $insertedEntities);
}
$obj->save($con);
return $obj->getPrimaryKey();
}
}
@@ -0,0 +1,90 @@
<?php
namespace Faker\ORM\Propel;
/**
* Service class for populating a database using the Propel ORM.
* A Populator can populate several tables using ActiveRecord classes.
*/
class Populator
{
protected $generator;
protected $entities = [];
protected $quantities = [];
public function __construct(\Faker\Generator $generator)
{
$this->generator = $generator;
}
/**
* Add an order for the generation of $number records for $entity.
*
* @param mixed $entity A Propel ActiveRecord classname, or a \Faker\ORM\Propel\EntityPopulator instance
* @param int $number The number of entities to populate
*/
public function addEntity($entity, $number, $customColumnFormatters = [], $customModifiers = [])
{
if (!$entity instanceof \Faker\ORM\Propel\EntityPopulator) {
$entity = new \Faker\ORM\Propel\EntityPopulator($entity);
}
$entity->setColumnFormatters($entity->guessColumnFormatters($this->generator));
if ($customColumnFormatters) {
$entity->mergeColumnFormattersWith($customColumnFormatters);
}
$entity->setModifiers($entity->guessModifiers($this->generator));
if ($customModifiers) {
$entity->mergeModifiersWith($customModifiers);
}
$class = $entity->getClass();
$this->entities[$class] = $entity;
$this->quantities[$class] = $number;
}
/**
* Populate the database using all the Entity classes previously added.
*
* @param PropelPDO $con A Propel connection object
*
* @return array A list of the inserted PKs
*/
public function execute($con = null)
{
if (null === $con) {
$con = $this->getConnection();
}
$isInstancePoolingEnabled = \Propel::isInstancePoolingEnabled();
\Propel::disableInstancePooling();
$insertedEntities = [];
$con->beginTransaction();
foreach ($this->quantities as $class => $number) {
for ($i = 0; $i < $number; ++$i) {
$insertedEntities[$class][] = $this->entities[$class]->execute($con, $insertedEntities);
}
}
$con->commit();
if ($isInstancePoolingEnabled) {
\Propel::enableInstancePooling();
}
return $insertedEntities;
}
protected function getConnection()
{
// use the first connection available
$class = key($this->entities);
if (!$class) {
throw new \RuntimeException('No class found from entities. Did you add entities to the Populator ?');
}
$peer = $class::PEER;
return \Propel::getConnection($peer::DATABASE_NAME, \Propel::CONNECTION_WRITE);
}
}
@@ -0,0 +1,112 @@
<?php
namespace Faker\ORM\Propel2;
use Propel\Generator\Model\PropelTypes;
use Propel\Runtime\Map\ColumnMap;
class ColumnTypeGuesser
{
protected $generator;
public function __construct(\Faker\Generator $generator)
{
$this->generator = $generator;
}
/**
* @return \Closure|null
*/
public function guessFormat(ColumnMap $column)
{
$generator = $this->generator;
if ($column->isTemporal()) {
if ($column->getType() == PropelTypes::BU_DATE || $column->getType() == PropelTypes::BU_TIMESTAMP) {
return static function () use ($generator) {
return $generator->dateTime;
};
}
return static function () use ($generator) {
return $generator->dateTimeAD;
};
}
$type = $column->getType();
switch ($type) {
case PropelTypes::BOOLEAN:
case PropelTypes::BOOLEAN_EMU:
return static function () use ($generator) {
return $generator->boolean;
};
case PropelTypes::NUMERIC:
case PropelTypes::DECIMAL:
$size = $column->getSize();
return static function () use ($generator, $size) {
return $generator->randomNumber($size + 2) / 100;
};
case PropelTypes::TINYINT:
return static function () use ($generator) {
return $generator->numberBetween(0, 127);
};
case PropelTypes::SMALLINT:
return static function () use ($generator) {
return $generator->numberBetween(0, 32767);
};
case PropelTypes::INTEGER:
return static function () use ($generator) {
return $generator->numberBetween(0, 2147483647);
};
case PropelTypes::BIGINT:
return static function () use ($generator) {
return $generator->numberBetween(0, PHP_INT_MAX);
};
case PropelTypes::FLOAT:
case PropelTypes::DOUBLE:
case PropelTypes::REAL:
return static function () use ($generator) {
return $generator->randomFloat();
};
case PropelTypes::CHAR:
case PropelTypes::VARCHAR:
case PropelTypes::BINARY:
case PropelTypes::VARBINARY:
$size = $column->getSize();
return static function () use ($generator, $size) {
return $generator->text($size);
};
case PropelTypes::LONGVARCHAR:
case PropelTypes::LONGVARBINARY:
case PropelTypes::CLOB:
case PropelTypes::CLOB_EMU:
case PropelTypes::BLOB:
return static function () use ($generator) {
return $generator->text;
};
case PropelTypes::ENUM:
$valueSet = $column->getValueSet();
return static function () use ($generator, $valueSet) {
return $generator->randomElement($valueSet);
};
case PropelTypes::OBJECT:
case PropelTypes::PHP_ARRAY:
default:
// no smart way to guess what the user expects here
return null;
}
}
}

Some files were not shown because too many files have changed in this diff Show More