Installation Add the bundle via Composer:
composer require atm/competitionbundle
Register the bundle in config/app.php under providers:
Altuma\CompetitionBundle\CompetitionBundle::class,
Publish Assets Run the following command to publish configuration and migrations:
php artisan vendor:publish --provider="Altuma\CompetitionBundle\CompetitionBundle" --tag="config"
php artisan vendor:publish --provider="Altuma\CompetitionBundle\CompetitionBundle" --tag="migrations"
Migrate the database:
php artisan migrate
First Use Case: Creating a Competition
Define a competition in your database via the competitions table or use the bundle’s API:
use Altuma\CompetitionBundle\Entity\Competition;
$competition = new Competition();
$competition->setName('Summer Sale 2024');
$competition->setStartDate(new \DateTime('2024-06-01'));
$competition->setEndDate(new \DateTime('2024-06-30'));
$competition->setDescription('Win discounts on all products!');
$competition->setStatus(Competition::STATUS_ACTIVE);
$em = $this->getDoctrine()->getManager();
$em->persist($competition);
$em->flush();
Routing
The bundle includes basic routes for listing and managing competitions. Ensure your routes.php includes:
use Altuma\CompetitionBundle\Resources\routing\competition_routing;
competition_routing($router);
Competition Management
Altuma\CompetitionBundle\Repository\CompetitionRepository) for database interactions.
$competitions = $competitionRepo->findBy(['status' => Competition::STATUS_ACTIVE]);
STATUS_DRAFT → STATUS_ACTIVE) with validation:
if ($competition->getStatus() === Competition::STATUS_DRAFT) {
$competition->setStatus(Competition::STATUS_ACTIVE);
$competition->setStartDate(new \DateTime());
}
Participation Logic
Participation entity or create a custom service to track user entries:
$participation = new \Altuma\CompetitionBundle\Entity\Participation();
$participation->setCompetition($competition);
$participation->setUser($this->getUser());
$participation->setEntryData(['product_id' => 123]);
$em->persist($participation);
isValid() method in a custom Participation class to enforce rules (e.g., one entry per user).Event-Driven Competitions
competition.activated) to trigger side effects:
// In a service or controller
$event = new CompetitionActivatedEvent($competition);
$this->get('event_dispatcher')->dispatch(
'competition.activated',
$event
);
competition.createdcompetition.updatedcompetition.deactivatedparticipation.submittedIntegration with Frontend
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
$normalizer = new ObjectNormalizer();
$competitionData = $normalizer->normalize($competition);
competition service:
{% for competition in competitions %}
<div class="competition-card">
<h3>{{ competition.name }}</h3>
<p>Ends: {{ competition.endDate|date('M d, Y') }}</p>
</div>
{% endfor %}
Scheduling
// In app/Console/Kernel.php
$schedule->call(function () {
$now = new \DateTime();
$competitionRepo->updateStatusesByDate($now);
})->daily();
Database Schema Assumptions
start_date, end_date). Custom migrations may conflict if not aligned.Status Transitions Without Validation
status without checking current state can lead to invalid transitions (e.g., STATUS_ACTIVE → STATUS_DRAFT).if (!$competition->canTransitionTo($newStatus)) {
throw new \RuntimeException('Invalid transition');
}
Participation Duplicates
(competition_id, user_id) or use a service to deduplicate:
$existing = $participationRepo->findOneBy([
'competition' => $competition,
'user' => $user,
]);
if ($existing) {
throw new \RuntimeException('Already participated');
}
Time Zone Handling
competitions table are stored as UTC. Ensure frontend/backend time zones are consistent.Carbon for conversions:
$localEndDate = $competition->getEndDate()->setTimezone(new \DateTimeZone('America/New_York'));
Event Dispatching Order
updated before created).Log Competition Events Add a subscriber to log events for debugging:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CompetitionLoggingSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
'competition.activated' => 'logEvent',
];
}
public function logEvent($event)
{
\Log::info('Competition activated', ['competition' => $event->getCompetition()]);
}
}
Check Repository Methods The bundle’s repository may not cover all use cases. Extend it:
class CustomCompetitionRepository extends ServiceEntityRepository
{
public function findActiveWithParticipants()
{
return $this->createQueryBuilder('c')
->leftJoin('c.participations', 'p')
->where('c.status = :status')
->setParameter('status', Competition::STATUS_ACTIVE)
->getQuery()
->getResult();
}
}
Configuration Overrides
Override bundle config in config/packages/altuma_competition.yaml:
altuma_competition:
default_status: STATUS_DRAFT # Override default
participation_limit: 100 # Per competition
Custom Participation Rules
Extend the Participation entity or create a trait:
namespace App\Entity;
use Altuma\CompetitionBundle\Entity\Participation as BaseParticipation;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Participation extends BaseParticipation
{
public function isValid(): bool
{
if (!$this->getUser()->hasPromoCode()) {
return false;
}
return parent::isValid();
}
}
Dynamic Competition Fields
Use doctrine extensions or a json column to store dynamic data:
$competition->setMetadata(['max_entries' => 50, 'prize' => '10% off']);
Third-Party Integrations
participation.submitted event.participation.validated.$event->getParticipation()->setPaymentStatus('paid');
\Stripe\Charge::create([...]);
Testing Use the bundle’s entities in PHPUnit tests:
public function testCompetitionCreation()
{
$competition = new Competition();
$competition
How can I help you explore Laravel packages today?