No description
Find a file
Nicholas Ciechanowski d4b19e8220 Update tests to use require_exercise helper
- All tests now use require_exercise() for path resolution
- Works in both user projects and track development
- Compatible with track subdirectory structure
2025-11-17 22:03:29 +11:00
exercises init 2025-11-10 22:59:02 +11:00
src Update to use exercises/example subdirectory structure 2025-11-17 21:53:44 +11:00
tests Update tests to use require_exercise helper 2025-11-17 22:03:29 +11:00
.gitignore init 2025-11-10 22:59:02 +11:00
composer.json Update documentation to use unified phpling binary 2025-11-11 22:37:35 +11:00
LICENSE init 2025-11-10 22:59:02 +11:00
README.md Update documentation to use unified phpling binary 2025-11-11 22:37:35 +11:00

Phpling Example Track

This is a template repository for creating your own Phpling tracks.

Use this as a starting point to create learning exercises for any framework, library, or programming topic.

What's Included

This example track contains:

  • 5 sample exercises with detailed inline comments
  • Corresponding test files with testing patterns
  • Fully documented TrackProvider implementation
  • Complete composer.json configuration
  • Best practices and tips for exercise design

How to Use This Template

Quick Start

  1. Copy this directory

    cp -r example-track my-awesome-track
    cd my-awesome-track
    
  2. Update composer.json

    • Change name to yourname/phpling-yourtrack
    • Update description
    • Change namespace from Phpling\\ExampleTrack to YourName\\PhplingYourTrack
    • Update the provider class name
  3. Update src/ExampleTrackProvider.php

    • Rename file to match your track (e.g., LaravelTrackProvider.php)
    • Update namespace
    • Change getName() to return your track identifier (e.g., 'laravel')
    • Update getDescription()
  4. Install dependencies

    composer install
    
  5. Create your exercises using the dev tools

    # Delete the example exercises
    rm exercises/*.php tests/exercises/*.php
    
    # Create your first exercise using the admin commands
    # (available because core-dev is installed as dev dependency)
    ./vendor/bin/phpling add 1 "Hello World"
    
    # Edit the generated files
    vim exercises/001_hello_world.php
    vim tests/exercises/Exercise001Test.php
    
    # Test it works
    ./vendor/bin/phpling check 1
    
  6. Test locally

    # In a separate directory
    composer init
    composer config repositories.local path ../my-awesome-track
    composer require yourname/phpling-yourtrack
    
  7. Publish to Packagist

    • Push to GitHub
    • Submit to packagist.org

File Structure

example-track/
├── src/
│   └── ExampleTrackProvider.php   # Implements TrackProvider interface
├── exercises/
│   ├── 001_hello.php              # Exercise files
│   ├── 002_variables.php
│   └── ...
├── tests/
│   └── exercises/
│       ├── Exercise001Test.php    # Test files
│       ├── Exercise002Test.php
│       └── ...
├── composer.json                   # Package configuration
└── README.md                       # This file

Exercise Design Guidelines

Naming Convention

Exercise files: NNN_topic_name.php

  • NNN = 3-digit number (001, 002, etc.)
  • topic_name = descriptive, lowercase with underscores
  • Examples: 001_hello.php, 015_route_parameters.php

Test files: ExerciseNNNTest.php

  • Must match the exercise number
  • Examples: Exercise001Test.php, Exercise015Test.php

Writing Good Exercises

  1. One Concept Per Exercise

    // ❌ BAD - Too many concepts
    // Exercise: Create a class with inheritance and implement an interface
    
    // ✅ GOOD - One concept
    // Exercise: Create a simple class with properties
    
  2. Progressive Difficulty

    • Exercise 001: Hello World
    • Exercise 002: Variables (uses echo from 001)
    • Exercise 003: Functions (uses variables from 002)
  3. Clear Instructions

    // ❌ BAD
    // Fix this code
    
    // ✅ GOOD
    // Create a function called greet() that:
    // - Takes one parameter: $name
    // - Returns "Hello, " followed by the name
    // Example: greet("Alice") returns "Hello, Alice"
    
  4. Use ??? for Missing Code

    function greet(???) {
        return ???;
    }
    
  5. Include Helpful Comments

    • Explain the concept being taught
    • Show syntax examples
    • Provide hints without giving away the answer

Writing Good Tests

  1. Test Behavior, Not Implementation

    // ❌ BAD - Tests implementation details
    test('uses a for loop', function () { ... });
    
    // ✅ GOOD - Tests the result
    test('returns array with correct values', function () { ... });
    
  2. Test Multiple Cases

    test('greet function works with different names', function () {
        expect(greet('Alice'))->toBe('Hello, Alice!');
        expect(greet('Bob'))->toBe('Hello, Bob!');
    });
    
  3. Test Edge Cases

    test('handles boundary conditions', function () {
        expect(checkAge(17))->toBe('Minor');
        expect(checkAge(18))->toBe('Adult');  // Boundary
        expect(checkAge(19))->toBe('Adult');
    });
    
  4. Use Descriptive Test Names

    // ❌ BAD
    test('test 1', function () { ... });
    
    // ✅ GOOD
    test('greet function returns correct greeting format', function () { ... });
    

Common Testing Patterns

Testing Output (echo/print)

test('outputs correct text', function () {
    ob_start();
    require __DIR__ . '/../../exercises/001_hello.php';
    $output = ob_get_clean();
    
    expect($output)->toBe('Hello, World!');
});

Testing Return Values

test('function returns correct value', function () {
    require_once __DIR__ . '/../../exercises/003_functions.php';
    
    expect(myFunction('input'))->toBe('expected output');
});

Testing Arrays

test('returns array with correct structure', function () {
    $result = require __DIR__ . '/../../exercises/005_arrays.php';
    
    expect($result)->toBeArray();
    expect($result)->toHaveCount(3);
    expect($result[0])->toBe('expected value');
});

Testing Exceptions

test('throws exception for invalid input', function () {
    require_once __DIR__ . '/../../exercises/010_errors.php';
    
    expect(fn() => riskyFunction())->toThrow(InvalidArgumentException::class);
});

Tips for Different Track Types

Framework Tracks (Laravel, Symfony, etc.)

  • Focus on framework-specific features
  • Use realistic examples from the framework
  • Consider including boilerplate code to reduce setup
  • Test using framework testing utilities

Example:

// Exercise: Creating a Route
use Illuminate\Support\Facades\Route;

Route::???(???, function () {
    return ???;
});

Library Tracks (Testing, ORMs, etc.)

  • Show library-specific patterns
  • Demonstrate best practices
  • Compare with non-library approaches

Concept Tracks (Design Patterns, Security, etc.)

  • Use generic, relatable examples
  • Show when and why to use the concept
  • Include anti-patterns (what NOT to do)

TrackProvider Interface

Your TrackProvider must implement these methods:

public function getName(): string
// Returns: unique track identifier (e.g., 'laravel')

public function getDescription(): string  
// Returns: human-readable description

public function getExercisesPath(): string
// Returns: absolute path to exercises directory

public function getTestsPath(): string
// Returns: absolute path to tests directory

public function getExercisesToCopy(): array
// Returns: ['source_path' => 'destination_path'] mapping

Advanced: Custom Installation Steps

If your track needs special setup (config files, directories, etc.):

// In composer.json
{
    "scripts": {
        "post-install-cmd": [
            "YourName\\PhplingYourTrack\\Installer::setup"
        ]
    }
}
// src/Installer.php
namespace YourName\PhplingYourTrack;

use Composer\Script\Event;

class Installer
{
    public static function setup(Event $event): void
    {
        $io = $event->getIO();
        
        // Create directories
        if (!is_dir('config')) {
            mkdir('config', 0755, true);
        }
        
        // Copy config files
        copy(__DIR__ . '/../stubs/config.php', 'config/yourtrack.php');
        
        $io->write('<info>Track setup complete!</info>');
    }
}

Testing Your Track Locally

Before publishing:

# Create a test project
mkdir test-project && cd test-project

# Configure composer to use your local track
composer init --no-interaction
composer config repositories.my-track path ../my-awesome-track
composer require yourname/phpling-yourtrack:@dev

# Test it works
ls exercises/  # Should contain your exercises

Publishing Checklist

  • Updated all names/namespaces in code
  • Deleted example exercises (or kept as reference)
  • Created your own exercises
  • Written tests for all exercises
  • Tested locally using path repository
  • Added LICENSE file
  • Updated this README for your track
  • Created GitHub repository
  • Tagged a release (v1.0.0)
  • Submitted to Packagist

Questions?

  • See the main CREATING_TRACKS.md for more detailed guidance
  • Check out php-track for a complete real-world example
  • Open an issue in the main Phpling repository

License

MIT


Happy teaching! 🚀

Replace this entire README with documentation specific to your track once you've customized it.