validateNip($nip) - Sprawdza poprawność - Invoice::validateNip('1234567890') → true normalizeNip($nip) - Usuwa myślniki, spacje - Invoice::normalizeNip('123-456-78-90') → '1234567890' formatNip($nip) - Formatuje XXX-XXX-XX-XX - Invoice::formatNip('1234567890') → '123-456-78-90' generateRandomNip() - Generuje losowy - NIP - Invoice::generateRandomNip() → '857-234-19-23' getTestNip() - Stały NIP testowy - Invoice::getTestNip() → '123-456-78-16' isForeignNip($nip) - Czy NIP zagraniczny - Invoice::isForeignNip('9912345678') → true getNipInfo($nip) - Debug info Zwraca tablicę z detailami seller_nip_formatted - Virtual field - $invoice->seller_nip_formatted → '123-456-78-90' buyer_nip_formatted - Virtual field - $invoice->buyer_nip_formatted → '123-456-78-90'
użycie w formularzach:
<!-- Formularz automatycznie normalizuje nip przy zapisie --> <?= $this->Form->control('seller_nip', [ 'label' => 'NIP Sprzedawcy', 'placeholder' => '123-456-78-90', 'help' => 'Można wpisać z myślnikami lub bez' ]) ?> <!-- Użytkownik wpisuje: "123-456-78-90" --> <!-- Zapisuje się: "1234567890" --> <!-- Wyświetla się: "123-456-78-90" -->
Encje:
use App\Model\Entity\Trait\NipTrait; class Invoice extends Entity { use NipTrait; .....
Tabela – walidacja
use App\Model\Entity\Invoice; ...... $validator ->scalar('seller_nip') ->maxLength('seller_nip', 10) ->minLength('seller_nip', 10) ->requirePresence('seller_nip', 'create') ->notEmptyString('seller_nip') ->numeric('seller_nip', 'NIP musi zawierać tylko cyfry') ->add('seller_nip', 'validNip', [ 'rule' => function ($value, $context) { return Invoice::validateNip($value); // ← UŻYJ TRAITA }, 'message' => 'Nieprawidłowy NIP sprzedawcy' ]);
Wyświetlanie – szablon view.php
<!-- Automatyczne formatowanie (virtual field) --> <p>NIP Sprzedawcy: <?= h($invoice->seller_nip_formatted) ?></p> <!-- Wyświetli: "123-456-78-90" --> <!-- Ręczne formatowanie --> <p>NIP: <?= \App\Model\Entity\Invoice::formatNip($nip) ?></p>
Użycie w kontrolerze:
// Walidacja NIP przed zapisem
if (!Invoice::validateNip($data['seller_nip'])) {
$this->Flash->error('Nieprawidłowy NIP!');
return;
}
// Normalizacja (automatycznie przez setter)
$invoice->seller_nip = '123-456-78-90';
// Zapisze się jako: "1234567890"
// Sprawdź czy zagraniczny
if (Invoice::isForeignNip($invoice->buyer_nip)) {
// To firma zagraniczna
}
Trait – definicja
<?php
declare(strict_types=1);
namespace App\Model\Entity\Trait;
/**
* NIP Trait
*
* Obsługa polskich numerów NIP:
* - Walidacja poprawności
* - Normalizacja (usuwanie myślników, spacji)
* - Formatowanie (XXX-XXX-XX-XX)
* - Generowanie przykładowych NIP-ów
*
* Użycie: w Invoice.php, PurchaseInvoice.php, Brand.php, Customer.php, etc.
*/
trait NipTrait
{
/**
* Waliduje numer NIP
*
* @param string|null $nip NIP do walidacji
* @return bool
*/
public static function validateNip(?string $nip): bool
{
if (empty($nip)) {
return false;
}
// Normalizuj - usuń wszystko oprócz cyfr
$nip = self::normalizeNip($nip);
// NIP musi mieć dokładnie 10 cyfr
if (strlen($nip) !== 10) {
return false;
}
// NIP nie może zaczynać się od 0
if ($nip[0] === '0') {
return false;
}
// Algorytm walidacji NIP (suma kontrolna)
$weights = [6, 5, 7, 2, 3, 4, 5, 6, 7];
$sum = 0;
for ($i = 0; $i < 9; $i++) {
$sum += (int)$nip[$i] * $weights[$i];
}
$checksum = $sum % 11;
// Jeśli suma kontrolna = 10, NIP jest nieprawidłowy
if ($checksum === 10) {
return false;
}
// Sprawdź czy ostatnia cyfra się zgadza
return $checksum == (int)$nip[9];
}
/**
* Normalizuje NIP - usuwa wszystkie znaki oprócz cyfr
*
* @param string|null $nip NIP z myślnikami, spacjami, etc.
* @return string NIP tylko cyfry (np. "1234567890")
*/
public static function normalizeNip(?string $nip): string
{
if (empty($nip)) {
return '';
}
// Usuń wszystko oprócz cyfr
return preg_replace('/[^0-9]/', '', $nip);
}
/**
* Formatuje NIP do postaci XXX-XXX-XX-XX
*
* @param string|null $nip NIP (z myślnikami lub bez)
* @return string Sformatowany NIP (np. "123-456-78-90")
*/
public static function formatNip(?string $nip): string
{
if (empty($nip)) {
return '';
}
// Normalizuj
$nip = self::normalizeNip($nip);
// Jeśli nie ma 10 cyfr, zwróć oryginalny
if (strlen($nip) !== 10) {
return $nip;
}
// Formatuj: XXX-XXX-XX-XX
returnsubstr($nip,0,3).'-'.
substr($nip,3,3).'-'.
substr($nip,6,2).'-'.
substr($nip, 8, 2);
}
/**
* Generuje losowy, POPRAWNY numer NIP (dla testów)
*
* @return string Wygenerowany NIP w formacie XXX-XXX-XX-XX
*/
public static function generateRandomNip(): string
{
// Wygeneruj losowe 9 cyfr (pierwsza nie może być 0)
$nip = (string)rand(1, 9); // Pierwsza cyfra: 1-9
for ($i = 1; $i < 9; $i++) {
$nip .= (string)rand(0, 9);
}
// Oblicz cyfrę kontrolną
$weights = [6, 5, 7, 2, 3, 4, 5, 6, 7];
$sum = 0;
for ($i = 0; $i < 9; $i++) {
$sum += (int)$nip[$i] * $weights[$i];
}
$checksum = $sum % 11;
// Jeśli checksum = 10, generuj ponownie
if ($checksum === 10) {
return self::generateRandomNip();
}
// Dodaj cyfrę kontrolną
$nip .= (string)$checksum;
// Zwróć sformatowany
return self::formatNip($nip);
}
/**
* Generuje przykładowy NIP dla firmy testowej
* (zawsze ten sam, poprawny NIP)
*
* @return string "123-456-78-16" (poprawny NIP testowy)
*/
public static function getTestNip(): string
{
return '123-456-78-16'; // Poprawny NIP testowy
}
/**
* Generuje tablicę przykładowych NIP-ów (dla seedów)
*
* @param int $count Liczba NIP-ów do wygenerowania
* @return array<string>
*/
public static function generateMultipleNips(int $count = 10): array
{
$nips = [];
for ($i = 0; $i < $count; $i++) {
$nips[] = self::generateRandomNip();
}
return $nips;
}
/**
* Pobiera sformatowany NIP dla danej encji
* (używane jako virtual field)
*
* @param string $field Nazwa pola (np. 'seller_nip', 'buyer_nip')
* @return string|null
*/
protected function getFormattedNip(string $field): ?string
{
if (empty($this->{$field})) {
return null;
}
return self::formatNip($this->{$field});
}
/**
* Virtual field: Sformatowany NIP sprzedawcy
* (dla Invoice i PurchaseInvoice)
*
* @return string|null
*/
protected function _getSellerNipFormatted(): ?string
{
return $this->getFormattedNip('seller_nip');
}
/**
* Virtual field: Sformatowany NIP nabywcy
* (dla Invoice i PurchaseInvoice)
*
* @return string|null
*/
protected function _getBuyerNipFormatted(): ?string
{
return $this->getFormattedNip('buyer_nip');
}
/**
* Setter: Automatyczna normalizacja NIP przy zapisie
* (wywołuje się automatycznie przy $entity->seller_nip = '123-456-78-90')
*
* @param string|null $value
* @return string|null
*/
protected function _setSellerNip(?string $value): ?string
{
return $value ? self::normalizeNip($value) : null;
}
/**
* Setter: Automatyczna normalizacja NIP nabywcy
*
* @param string|null $value
* @return string|null
*/
protected function _setBuyerNip(?string $value): ?string
{
return $value ? self::normalizeNip($value) : null;
}
/**
* Sprawdza czy NIP należy do podmiotu zagranicznego
* (NIP zagraniczny zaczyna się od "99")
*
* @param string|null $nip
* @return bool
*/
public static function isForeignNip(?string $nip): bool
{
if (empty($nip)) {
return false;
}
$normalized = self::normalizeNip($nip);
return substr($normalized, 0, 2) === '99';
}
/**
* Pobiera informacje o NIP-ie (helper do debugowania)
*
* @param string|null $nip
* @return array
*/
public static function getNipInfo(?string $nip): array
{
$normalized = self::normalizeNip($nip);
return [
'original' => $nip,
'normalized' => $normalized,
'formatted' => self::formatNip($nip),
'valid' => self::validateNip($nip),
'length' => strlen($normalized),
'is_foreign' => self::isForeignNip($nip),
];
}
} // end NipTrait
W testach:
// Wygeneruj losowy NIP $testNip = Invoice::generateRandomNip(); // "857-234-19-23" // Pobierz stały NIP testowy $nip = Invoice::getTestNip(); // "123-456-78-16" // Wygeneruj wiele NIP-ów $nips = Invoice::generateMultipleNips(50); // ['123-456-78-16', '857-234-19-23', ...] // Debug info $info = Invoice::getNipInfo('123-456-78-90'); /* [ 'original' => '123-456-78-90', 'normalized' => '1234567890', 'formatted' => '123-456-78-90', 'valid' => true, 'length' => 10, 'is_foreign' => false, ] */