Installation
composer require controleonline/contract
Publish the package configuration and migrations:
php artisan vendor:publish --provider="ControleOnline\Contract\ContractServiceProvider"
php artisan migrate
First Use Case: Basic Contract Creation
Contract model extending ControleOnline\Contract\Models\Contract:
use ControleOnline\Contract\Models\Contract;
class MyContract extends Contract
{
// Custom fields or logic
}
$contract = MyContract::create([
'title' => 'Service Agreement',
'start_date' => now(),
'end_date' => now()->addYear(),
'status' => 'draft', // or 'signed', 'expired'
]);
Key Files to Review
config/contract.php: Core settings (e.g., document templates, signature workflows).database/migrations/: Schema for contracts, contract_people, and related tables.src/Models/Contract.php: Base model with core fields (title, status, dates).src/Models/ContractPeople.php: Junction table for contract-participant relationships.Contract-Participant Relationships
Attach people (users, clients) to contracts via ContractPeople:
$contract->people()->attach($userId, [
'role' => 'client', // 'signatory', 'witness', etc.
'signed_at' => now(),
]);
hasManyThrough or query scopes for common lookups:
$contract->people->where('role', 'signatory');
Document Generation
Leverage the generateDocument() trait method:
$pdf = $contract->generateDocument(); // Returns a PDF stream or path
getDocumentTemplate() in your model:
protected function getDocumentTemplate(): string
{
return 'custom.contract_template';
}
Signature Workflow
event(new ContractNeedsSignature($contract));
integration/ layer:
// Example: Extend the base service
class CustomSignatureService extends \ControleOnline\Contract\Services\SignatureService
{
public function sendForSignature($contract): void
{
// Custom logic (e.g., API call to external service)
}
}
Status Transitions
Use the status field to track lifecycle (e.g., draft → signed):
$contract->update(['status' => 'signed']);
class ContractObserver
{
public function saving(Contract $contract)
{
if ($contract->isDirty('status') && $contract->status === 'signed') {
$contract->people()->update(['signed_at' => now()]);
}
}
}
Contract and ContractPeople as resources:
# config/api_platform/resources.yaml
resources:
ControleOnline\Contract\Models\Contract:
collectionOperations:
- 'get'
- 'post'
itemOperations:
- 'get'
- 'put'
- 'generate_document' # Custom operation
Contract::created(function ($contract) {
// Send notification, log, etc.
});
SignatureService for unit tests:
$this->app->instance(
\ControleOnline\Contract\Services\SignatureService::class,
Mockery::mock()
);
Document Generation Assumptions
laravel-dompdf).barryvdh/laravel-dompdf and configure the template path in config/contract.php:
'document_templates' => resource_path('views/contracts'),
Signature Provider Coupling
integration/ layer is abstract, but the base SignatureService expects specific methods.Status Field Validation
status field uses a limited set of values (draft, signed, expired).Status enum:
use ControleOnline\Contract\Enums\ContractStatus;
$contract->update(['status' => ContractStatus::SIGNED]);
Migration Conflicts
php artisan migrate --path=vendor/controleonline/contract/database/migrations
php artisan migrate
config/contract.php:
'log_events' => true,
dd($contract->getDocumentTemplate()) to verify template paths.contract_signature_requests table for stuck requests:
SELECT * FROM contract_signature_requests WHERE status = 'pending';
Custom Fields
Add fields to the contracts table via migration, then extend the model:
class MyContract extends Contract
{
protected $casts = [
'custom_field' => 'boolean',
];
}
Signature Providers
Register a custom provider in config/contract.php:
'signature_providers' => [
'default' => \App\Services\CustomSignatureService::class,
],
Document Types
Support multiple document types (e.g., PDF, Word) by extending the generateDocument() method:
public function generateDocument(): string
{
if ($this->type === 'word') {
return $this->generateWordDocument();
}
return parent::generateDocument();
}
Validation Rules Override validation in your model:
public static function boot()
{
parent::boot();
static::addGlobalScope('customValidation', function (Builder $builder) {
$builder->where('end_date', '>=', now());
});
}
How can I help you explore Laravel packages today?