$30 off During Our Annual Pro Sale. View Details »

Type Safety and Compilation Checks: How Static Analysis Can Benefit Your Team

Type Safety and Compilation Checks: How Static Analysis Can Benefit Your Team

Numerous technologies exist for creating web applications, with PHP and JavaScript being the most prevalent. Their dynamic typing and runtime execution capabilities allow for a seamless transition from prototype to finished product. However, as apps grow in complexity and significance in our everyday lives, the absence of type safety and compilation checks might be felt. In this presentation, we will explore the concept of static analysis and its potential to enhance your team's software development process.

Hunter Skrasek

April 13, 2023
Tweet

More Decks by Hunter Skrasek

Other Decks in Programming

Transcript

  1. Type Safety and
    Compilation
    Checks:
    How Static Analysis Can Benefit Your Team

    View Slide

  2. What is dynamic
    typing?

    View Slide

  3. function createUser($name, $age) {
    // Other complex business logic
    return new User($name, $age)
    }

    View Slide

  4. createUser('Hunter', 30);
    createUser('Joe', '24');
    createUser('Franke', true);
    createUser(42, 'age');

    View Slide

  5. var_dump(createUser('Franke', true));
    => User {#2674
    +name: "Franke",
    +age: 1,
    }
    var_dump(createUser(42, 'age));
    => User {#2670
    +name: "42",
    +age: 0,
    }

    View Slide

  6. console.log(3 < 2 < 1); // true (huh?)
    console.log(false < 1);

    View Slide

  7. How do we
    catch these
    types of bugs?

    View Slide

  8. Static Analysis

    View Slide

  9. Ok great, but how?

    View Slide

  10. PHPStan

    View Slide

  11. View Slide

  12. View Slide

  13. Psalm

    View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. Other cool features
    Security Analysis

    Code Manipulation

    View Slide

  18. ------ ------------------------------------------------------
    -------------------------------------------------------------
    -------------------
    Line ValueObjects/PhoneIntelligence.php
    ------ -----------------------------------------------------
    -------------------------------------------------------------
    --------------------
    45 Parameter $lineType of class
    App\ValueObjects\PhoneIntelligence constructor expects
    App\Enum\LineType, App\Enum\LineType|null given.
    79 Method App\ValueObjects\PhoneIntelligence::toJson()
    should return string but returns string|false.
    ------ -----------------------------------------------------
    -------------------------------------------------------------
    --------------------

    View Slide

  19. declare(strict_types=1);
    namespace App\ValueObjects;
    class PhoneIntelligence implements Arrayable, Castable, Jsonable
    {
    public function __construct(
    private readonly ?string $formatted,
    private readonly ?string $carrier,
    private readonly LineType $lineType = LineType::Unknown
    ) {
    }
    public static function castUsing(array $arguments): CastsAttributes
    {
    return new class implements CastsAttributes {
    public function get($model, string $key, $value, array $attributes): PhoneIntelligence
    {
    return new PhoneIntelligence(
    formatted: $attributes['phone_formatted'],
    carrier: $attributes['phone_carrier'],
    lineType: LineType::tryFrom($attributes['phone_type'] ?? 'unknown'),
    );
    }
    };
    }
    public function toJson($options = 0)
    {
    return json_encode($this->toArray(), $options);
    }
    }

    View Slide

  20. declare(strict_types=1);
    namespace App\ValueObjects;
    class PhoneIntelligence implements Arrayable, Castable, Jsonable
    {
    public function __construct(
    private readonly ?string $formatted,
    private readonly ?string $carrier,
    private readonly LineType $lineType = LineType::Unknown
    ) {
    }
    public static function castUsing(array $arguments): CastsAttributes
    {
    return new class implements CastsAttributes {
    public function get($model, string $key, $value, array $attributes): PhoneIntelligence
    {
    return new PhoneIntelligence(
    formatted: $attributes['phone_formatted'],
    carrier: $attributes['phone_carrier'],
    lineType: LineType::from($attributes['phone_type'] ?? 'unknown'),
    );
    }
    };
    }
    public function toJson($options = 0)
    {
    return json_encode($this->toArray(), JSON_THROW_ON_ERROR | $options);
    }
    }

    View Slide

  21. View Slide

  22. What about
    frontend
    development?

    View Slide

  23. Introducing:
    TypeScript

    View Slide

  24. TypeWhat?

    View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. interface Account {
    id: number
    displayName: string
    version: 1
    }
    function welcome(user: Account) {
    console.log(user.id)
    }
    type Result = "pass" | "fail"
    function verify(result: Result) {
    if (result === "pass") {
    console.log("Passed")
    } else {
    console.log("Failed")
    }
    }

    View Slide

  30. So how can we
    use these tools?

    View Slide

  31. composer require --dev phpstan/phpstan
    composer require --dev vimeo/psalm

    View Slide

  32. - run:
    name: "Perform a static analysis of application"
    command: |
    set -x
    git diff origin/master... > ~/test-results/phpstan/diff.txt
    if [ ! -s ~/test-results/phpstan/files.txt ]; then
    exit 0
    fi
    docker compose run --rm app ./vendor/bin/phpstan analyse \
    --memory-limit=-1 --error-format=junit --no-interaction --no-progress
    --no-ansi 1> ~/test-results/phpstan/junit.xml || true
    docker compose run --rm app ./vendor/bin/phpstan analyse \
    --memory-limit=-1 --no-interaction --no-progress --no-ansi > ~/test-
    results/phpstan/phpstan.txt
    docker compose run --rm -v ~/test-results/phpstan:/test-reports app
    ./vendor/bin/diffFilter \
    --phpstan /test-reports/diff.txt /test-reports/phpstan.txt 100

    View Slide

  33. npm i typescript
    import axios, {AxiosResponse, AxiosStatic} from 'axios';
    import {API_DOMAIN} from './constants';
    import {Homeowner} from './homeowner';
    import {Project} from './project';
    import Contractor from './contractor';
    import ExternalMatch from './external-match';
    import {parse} from 'date-fns';
    import * as _ from 'lodash';
    class ModernizeClient {
    private axios: AxiosStatic;
    constructor() {
    this.axios = axios;
    this.axios.defaults.baseURL = API_DOMAIN;
    }
    public projectDetails(projectId: string): Promise {
    return axios.get('/v1/projects/' + projectId)
    .then(response => {
    let responseData = response.data.data;
    let homeowner = new Homeowner(responseData.homeowner.data.firstName, responseData.homeowner.data.lastName,
    responseData.homeowner.data.phoneNumber);
    return Promise.resolve(
    new Project(homeowner, responseData.projectType, parse(responseData.dateCreated), parse(responseData.startDate))
    );
    });
    }
    }
    export {ModernizeClient}

    View Slide

  34. ESLint npm install --save-dev eslint
    @typescript-eslint/parser
    @typescript-eslint/eslint-plugin
    eslint . --ext .ts
    {
    "root": true,
    "parser": "@typescript-
    eslint/parser",
    "plugins": [
    "@typescript-eslint"
    ],
    "extends": [
    "eslint:recommended",
    "plugin:@typescript-
    eslint/eslint-recommended",
    "plugin:@typescript-
    eslint/recommended"
    ]
    }

    View Slide

  35. WHAT ABOUT
    EXISTING
    PROJECTS?

    View Slide

  36. PHP
    PHPStan
    vendor/bin/phpstan analyse --
    level 9 \
    --configuration phpstan.neon \
    src/ tests/ --generate-baseline
    Psalm
    vendor/bin/psalm --set-
    baseline=your-baseline.xml
    vendor/bin/psalm --update-
    baseline
    Configuration
    includes:
    - phpstan-baseline.neon
    parameters:
    # your usual configuration
    options

    ...
    errorBaseline="./path/to/your-
    baseline.xml"
    >
    ...

    View Slide

  37. JavaScript
    TypeScript

    View Slide

  38. {
    "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "allowJs": true,
    "checkJs": false,
    "outDir": "dist",
    "rootDir": ".",
    "strict": false,
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports.
    Implies 'allowSyntheticDefaultImports'. */,
    "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
    "declaration": true, /* Generates corresponding '.d.ts' file. */
    "strictNullChecks": true,
    "resolveJsonModule": true,
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
    "*": [
    "*",
    "src/*",
    "src/setup/*",
    "src/logic/*",
    "src/models/*",
    "config/*"
    ]
    },
    },
    "exclude": ["node_modules", "dist"],
    "include": [
    "./src",
    "./test",
    "./*",
    "./config"
    ]
    }

    View Slide

  39. /node_modules/typescript/bin/tsc

    View Slide

  40. Closing
    Time

    View Slide

  41. I've been:
    Hunter
    Skrasek

    View Slide

  42. Where do I reside?
    Mastodon:
    [email protected]
    Email: [email protected]

    View Slide

  43. Questions?

    View Slide