Our approach
The software engineering standards we follow
How we design, write, test, release and protect software, told through the international standards and methods we follow. Every item links to its official source.
For every item we say we apply, the evidence is in the source code, the configuration or the tests of this site, and we describe below the item how we apply it. Items without such evidence yet appear as references only.
Process and quality
The methods that decide when work is done, how it is reviewed and how it is documented.
-
Definition of Done
We apply itA shared, explicit statement of what must be true before work counts as finished.
How we apply it The same check runs before every change: template, YAML and service container lints, static analysis, the tests and a check of the live pages. If one of them fails, the work is not done.
Official source: Definition of Done (opens in a new tab) -
Docs as code
We apply itDocumentation lives in the repository as plain text and goes through the same review as code.
How we apply it Architecture decisions, module documents, release steps and work plans are kept in the repository as Markdown and updated in the same change as the code.
Official source: Docs as code (opens in a new tab) -
Code review
We apply itEvery change is read by someone other than its author before it merges.
How we apply it Changes go through a written review checklist, and some are merged through a GitLab merge request. On work with a single developer the review is tool-assisted, not done by a second person.
Official source: Code review (opens in a new tab) -
Agile Manifesto
We apply itFour values and twelve principles for iterative, collaborative delivery.
How we apply it We release work in small increments, measure the result on the live system and keep the owner's decisions in writing at every stage.
Official source: Agile Manifesto (opens in a new tab) -
Diátaxis documentation framework
We apply itA framework that splits documentation into tutorials, how-to guides, reference and explanation.
How we apply it The reasons behind decisions, the module reference documents and the step-by-step release guides live in separate places; the documents are not labeled by type and there are no tutorials.
Official source: Diátaxis documentation framework (opens in a new tab) -
ISO/IEC 25010:2023 product quality model
ReferenceA model that defines software quality in nine characteristics, from functional suitability to security and maintainability.
Official source: ISO/IEC 25010:2023 product quality model (opens in a new tab)
Architecture and design
How we build the parts of an application and its connections to other systems.
-
The Twelve-Factor App
We apply itTwelve rules for portable services: configuration in the environment, build, release and run kept apart, development and production alike.
How we apply it Configuration is read from environment variables, the package is built once and released, and development, testing and production use the same PHP version. The one deliberate exception: logs go to files because of how the server works.
Official source: The Twelve-Factor App (opens in a new tab) -
Architecture Decision Records
We apply itShort numbered documents with context, decision and consequences, kept next to the code.
How we apply it Decisions such as the stack, the fonts, the release pipeline, market-based localization and the language policy are kept in the repository as numbered decision records.
Official source: Architecture Decision Records (opens in a new tab) -
Hexagonal architecture (ports and adapters)
We apply itThe core of an application talks to the outside world only through ports; adapters implement them for mail, HTTP and third-party services.
How we apply it We use ports, and adapters that implement them, for email, the CRM and bot verification; there is no test that checks the dependency rule yet.
Official source: Hexagonal architecture (ports and adapters) (opens in a new tab) -
Clean Architecture
We apply itDependencies point inward toward the business rules; the framework and input and output stay at the edge.
How we apply it Inputs and results travel in immutable data objects, and the services that run the business logic do not know the HTTP request.
Official source: Clean Architecture (opens in a new tab) -
HTTP Semantics (RFC 9110) and 429 Too Many Requests (RFC 6585)
We apply itHTTP methods, status codes, content negotiation and headers such as Retry-After.
How we apply it The contact form answers each outcome with the right status code: 200, 303, 403, 422, 503 and 429 with Retry-After; we honor the Retry-After header of the remote service as well.
Official source: HTTP Semantics (RFC 9110) and 429 Too Many Requests (RFC 6585) (opens in a new tab) -
Timeouts, retries with backoff and queues
We apply itRemote calls get a timeout, failed calls are retried later with growing delays, and the work survives in a queue meanwhile.
How we apply it The CRM call has a five-second timeout; on a temporary error the submission is queued and retried by a scheduled job. Email and the CRM work independently of each other.
Official source: Timeouts, retries with backoff and queues (opens in a new tab) -
OAuth 2.0 (RFC 6749)
We apply itAn authorization framework in which a server-side client exchanges its credentials for an access token.
How we apply it This site connects to the ONLINEKURUM API with client credentials, keeps the token for its lifetime and refreshes it once on a 401 response.
Official source: OAuth 2.0 (RFC 6749) (opens in a new tab) -
REST and the Richardson Maturity Model
We apply itStateless client-server interaction over cacheable resources; the model grades HTTP APIs by maturity level.
How we apply it The site is stateless, its GET pages can be cached and the form answers on a single resource with a separate status code per outcome.
Official source: REST and the Richardson Maturity Model (opens in a new tab) -
Domain-Driven Design
We apply itBuilding software on a model of the business domain with a shared language, value objects and explicit boundaries.
How we apply it We name our classes after the business domain and model concepts such as the language as value objects; the site is one small context, so there are no separate boundaries.
Official source: Domain-Driven Design (opens in a new tab) -
Semantic Versioning 2.0.0
We apply itMAJOR.MINOR.PATCH numbers whose parts mean an incompatible change, a new feature or a fix.
How we apply it We declare dependencies with semantic version constraints; the site itself carries no version tags.
Official source: Semantic Versioning 2.0.0 (opens in a new tab)
Code quality and style
The standards and tools that keep code readable, consistent and type safe.
-
Strict types in PHP (strict_types)
We apply itdeclare(strict_types=1) turns silent type coercion into a TypeError.
How we apply it Every class file in the application and test code, except the framework's Kernel class, starts with strict types, and the review checklist checks it.
Official source: Strict types in PHP (strict_types) (opens in a new tab) -
Static analysis with PHPStan
We apply itAnalysis without running the code, with rule levels that set how strictly it is checked.
How we apply it PHPStan scans the application, test and configuration code at level 6; it runs before every change and in continuous integration.
Official source: Static analysis with PHPStan (opens in a new tab) -
PER Coding Style 3.0 (successor of PSR-12)
We apply itThe living formatting standard of PHP-FIG that replaced PSR-12 and that Symfony's coding standard builds on.
How we apply it We write code in this style: strict types first, four spaces through EditorConfig, Symfony conventions. There is no automated fixer yet, so the standard is applied but not enforced.
Official source: PER Coding Style 3.0 (successor of PSR-12) (opens in a new tab) -
PSR-4 autoloading
We apply itMaps a namespace prefix to a directory, so a class is found from its fully qualified name.
How we apply it Application and test classes are loaded through Composer with PSR-4.
Official source: PSR-4 autoloading (opens in a new tab) -
Symfony Best Practices
We apply itThe official guide that recommends thin controllers, services for logic, attributes for routing and environment variables for infrastructure.
How we apply it Controller actions stay under twenty lines, routes are defined with attributes and infrastructure settings come from environment variables.
Official source: Symfony Best Practices (opens in a new tab) -
SOLID principles
We apply itSingle responsibility, open-closed, Liskov substitution, interface segregation and dependency inversion.
How we apply it Services do one job and depend on interfaces for outside dependencies such as email, the CRM, bot verification and the language settings; no architecture test checks this.
Official source: SOLID principles (opens in a new tab) -
BEM naming for CSS
We apply itA naming method that ties styles to one component through block, element and modifier names.
How we apply it Our components are named with BEM, and a test checks that every class used in a template is defined in a stylesheet.
Official source: BEM naming for CSS (opens in a new tab) -
EditorConfig
We apply itA file that keeps the charset, the indentation and the line endings the same in every editor.
How we apply it The EditorConfig file in the repository sets UTF-8, LF line endings, four-space indentation and a final newline.
Official source: EditorConfig (opens in a new tab) -
PSR-1 Basic Coding Standard
We apply itBasic rules for PHP files: one declaration per file, class and method naming, no side effects next to declarations.
How we apply it Application and test code is written to these rules; there is no automated checker.
Official source: PSR-1 Basic Coding Standard (opens in a new tab) -
PSR-3 Logger Interface
We apply itA logging interface, so services depend on a shared interface rather than on a library.
How we apply it Services receive the PSR-3 interface for logging; contact records are kept in a separate channel.
Official source: PSR-3 Logger Interface (opens in a new tab) -
PSR-11 Container Interface
We apply itA shared interface for dependency injection containers.
How we apply it Services are wired automatically through their constructors by the Symfony container.
Official source: PSR-11 Container Interface (opens in a new tab) -
Clean Code
We apply itReadable names, small functions, no duplication and comments that explain why rather than what.
How we apply it We use immutable services and data objects, intention-revealing names and class comments that record the reasons behind decisions; no tool measures this.
Official source: Clean Code (opens in a new tab)
Testing
How we prove that a change does not break anything.
-
Test pyramid
We apply itMany fast unit tests, fewer integration tests and very few end-to-end tests.
How we apply it The tests run in two suites, unit and integration; on top of them sits a single HTTP check against the live pages.
Official source: Test pyramid (opens in a new tab) -
xUnit tests and application tests (PHPUnit)
We apply itAutomated tests with fixtures and assertions; application tests send requests through the real kernel.
How we apply it PHPUnit fails a test on a deprecation, a notice or a warning, and the integration tests render the pages through the real kernel in both markets.
Official source: xUnit tests and application tests (PHPUnit) (opens in a new tab) -
Architectural fitness functions (guard tests)
We apply itAutomated checks that keep an architectural or project rule true as the code changes.
How we apply it Guard tests cover the coverage of style classes, the copy rules, the translation parity of both markets, English URLs, the page cache and the content security policy; the guards for style classes, copy rules, translation parity and English URLs also prove that their own check can fail.
Official source: Architectural fitness functions (guard tests) (opens in a new tab) -
Smoke testing
We apply itA small set of checks that the main function of a build works before anything else is tested.
How we apply it The same list of addresses is called locally and on the live site after every release; every page must carry a marker that only PHP produces.
Official source: Smoke testing (opens in a new tab) -
Test-Driven Development
We apply itWrite a failing test, make it pass, then refactor.
How we apply it Every change ships with its tests, and every service and data object has a test; whether the test was written before the code is not recorded.
Official source: Test-Driven Development (opens in a new tab) -
Behavior-Driven Development
We apply itDescribe behavior in business language and derive the tests from it.
How we apply it We write our tests with names that describe the behavior; we do not use Behat or Gherkin.
Official source: Behavior-Driven Development (opens in a new tab) -
Accessibility evaluation (WCAG-EM)
We apply itA method and tools for checking how a site conforms to WCAG.
How we apply it We measured the Lighthouse accessibility score and recorded the result (100); there is no automated accessibility test in continuous integration yet.
Official source: Accessibility evaluation (WCAG-EM) (opens in a new tab)
Delivery and operations
How code travels from the repository to the live system and how it runs there.
-
Continuous Integration
We apply itEvery push is built and tested automatically against the shared branch.
How we apply it On every branch the continuous integration job runs the lints, the static analysis and the tests.
Official source: Continuous Integration (opens in a new tab) -
Continuous Delivery
We apply itSoftware is always releasable; one package is built once and moves through an automated pipeline.
How we apply it The package is built once and that package goes live; the release scripts can be rehearsed end to end against a fake server.
Official source: Continuous Delivery (opens in a new tab) -
Zero-downtime release and rollback
We apply itA new release is prepared next to the live one, traffic moves with one link switch, and rollback is the switch back.
How we apply it Every release is prepared in its own folder and goes live with a single link switch; the last five releases are kept and a rollback is one step.
Official source: Zero-downtime release and rollback (opens in a new tab) -
Conventional Commits 1.0.0
We apply itCommit subjects carry a type and a scope, so the history is readable by people and tools.
How we apply it Our commit subjects use types such as feat, fix, docs and test; no hook enforces it.
Official source: Conventional Commits 1.0.0 (opens in a new tab) -
Feature toggles
We apply itBehavior is switched on and off through configuration rather than through a release.
How we apply it The page cache, bot verification and the CRM connection are switched by environment variables; the cache first shipped switched off and was turned on after testing.
Official source: Feature toggles (opens in a new tab) -
Infrastructure as code and containers
We apply itEnvironments are defined in versioned files and built by tools, not by hand.
How we apply it The development environment is defined in Docker files, the release pipeline in versioned configuration, and no file on the server is edited by hand; the first setup of the hosting account was done by hand.
Official source: Infrastructure as code and containers (opens in a new tab) -
Git-flow branching model
We apply itA branching model in which features gather in a development branch and releases are kept on the main branch.
How we apply it Feature branches merge into the development branch and the development branch into the main branch; we follow a simplified form of the model, without release or hotfix branches and without version tags.
Official source: Git-flow branching model (opens in a new tab) -
GitOps principles 1.0
We apply itThe desired state is declared in Git and applied automatically; Git is the only path to production.
How we apply it A push to the main branch is the only way to release, and the server configuration is versioned in the repository; no agent keeps the server in sync with Git continuously.
Official source: GitOps principles 1.0 (opens in a new tab) -
Site Reliability Engineering
We apply itRunning services with service level objectives, monitoring, incident response and blameless postmortems.
How we apply it We use a health check and a rollback path after every release and rotating logs with retention; there is no uptime monitoring and no service level objective defined.
Official source: Site Reliability Engineering (opens in a new tab)
Security and privacy
The guides and the legal framework we follow to protect the application, the user's data and the release process.
-
Content Security Policy Level 3
We apply itA header that lists the allowed sources for scripts, styles and frames, which blocks injected inline code.
How we apply it The live policy has no unsafe-inline, we use no inline styles or event handlers, and a test guards the policy.
Official source: Content Security Policy Level 3 (opens in a new tab) -
HTTP security headers and HSTS
We apply itStrict-Transport-Security, X-Content-Type-Options, Referrer-Policy, Permissions-Policy and frame-ancestors on every response.
How we apply it All of these headers are set on the live server and HTTP requests are permanently redirected to HTTPS; local development runs on HTTPS as well.
Official source: HTTP security headers and HSTS (opens in a new tab) -
OWASP Cheat Sheet Series
We apply itShort implementation guides on topics such as CSRF prevention, input validation, logging and secrets.
How we apply it The form uses stateless CSRF protection with an Origin check and a double-submit cookie; input is validated against an allowlist and cleaned of control characters, and email addresses are masked in the logs.
Official source: OWASP Cheat Sheet Series (opens in a new tab) -
Rate limiting and anti-automation
We apply itRequest limits and bot traps protect forms from abuse.
How we apply it The contact form is limited to five submissions per IP address in fifteen minutes; it also has a hidden trap field and bot verification, and the order of the checks is written down in the class comment.
Official source: Rate limiting and anti-automation (opens in a new tab) -
Least privilege
We apply itEvery program and user runs with the smallest set of rights the task needs.
How we apply it Releases use a restricted key that belongs to continuous integration only, production secrets sit in a file only its owner can read, and only the entry point can be executed.
Official source: Least privilege (opens in a new tab) -
Secrets management and secret detection
We apply itSecrets stay out of the repository, are scoped to the environment, and pushes are scanned for leaks.
How we apply it The configuration in the repository holds no secrets, production secrets live in protected continuous integration variables, and automated secret detection runs in the pipeline.
Official source: Secrets management and secret detection (opens in a new tab) -
Privacy by design and by default
We apply itData protection built into the design from the start: collect the minimum, protect by default.
How we apply it Public pages use no cookies and no sessions, analytics loads only after consent, and contact records are kept for a limited time with masked email addresses.
Official source: Privacy by design and by default (opens in a new tab) -
KVKK, Law No. 6698 on the Protection of Personal Data
We apply itTürkiye's personal data law, including the duty to inform and the consent rules.
How we apply it The Turkish market publishes its information notice and the form asks for explicit consent; the legal review of the notice is pending.
Official source: KVKK, Law No. 6698 on the Protection of Personal Data (opens in a new tab) -
GDPR, Regulation (EU) 2016/679
We apply itThe data protection regulation of the European Union: lawfulness, data minimization, transparency and the rights of data subjects.
How we apply it The English market publishes its own privacy notice structured after the GDPR, and a test checks that each market carries its own notice; the legal review is pending.
Official source: GDPR, Regulation (EU) 2016/679 (opens in a new tab) -
OWASP Top 10:2025
We apply itThe ten most critical risk categories for web applications.
How we apply it We have controls against several categories: access restrictions, security headers, validation and automatic escaping, security logs. We are guided by the list; there is no category-by-category assessment document.
Official source: OWASP Top 10:2025 (opens in a new tab) -
CWE Top 25 (2025)
We apply itThe 25 most dangerous software weakness types, ranked by CVE data.
How we apply it Against XSS we rely on the automatic escaping of the template engine, against CSRF on token protection; we are guided by the list and keep no weakness mapping.
Official source: CWE Top 25 (2025) (opens in a new tab) -
SLSA 1.2 (supply chain levels)
We apply itLevels for build provenance and source integrity in the software supply chain.
How we apply it Packages are built on a hosted runner from a versioned pipeline, and every package records the commit it came from; we produce no provenance attestation and no level has been assessed.
Official source: SLSA 1.2 (supply chain levels) (opens in a new tab) -
Cookie consent (KVKK cookie guideline and ePrivacy Directive)
We apply itNon-essential cookies are set only after a clear notice and consent.
How we apply it Analytics cookies load only after "Allow" is chosen; the consent banner follows the rule of each market and is guarded by tests.
Official source: Cookie consent (KVKK cookie guideline and ePrivacy Directive) (opens in a new tab) -
Email authentication: SPF, DKIM and DMARC
We apply itDNS records that let receivers verify the sending domain.
How we apply it Form email is sent from our own domain with the visitor as the reply-to address; the DNS checklist is written down in the release documentation.
Official source: Email authentication: SPF, DKIM and DMARC (opens in a new tab)
Web standards, performance and accessibility
So that a page opens fast on every device, works for everyone and reads correctly in every language.
-
WCAG 2.2
We apply itSuccess criteria for accessible content at levels A, AA and AAA.
How we apply it We build pages to these criteria: a skip link, the page language, visible focus and reduced motion support; no formal conformance audit has been done.
Official source: WCAG 2.2 (opens in a new tab) -
WAI-ARIA 1.2
We apply itRoles, states and properties that expose the changing parts of an interface to assistive technology.
How we apply it Our templates use aria-labelledby, aria-describedby, aria-expanded, aria-controls, aria-live and role="status".
Official source: WAI-ARIA 1.2 (opens in a new tab) -
Core Web Vitals and Lighthouse
We apply itThe LCP, INP and CLS thresholds and the audit tool that reports them.
How we apply it We recorded a Lighthouse measurement (home page: 97 on mobile, 100 on desktop); we preload fonts, defer scripts and serve only the icons in use.
Official source: Core Web Vitals and Lighthouse (opens in a new tab) -
HTML Living Standard (semantic HTML)
We apply itElements with meaning, such as main, nav, header, footer, section, article and dialog, instead of generic containers.
How we apply it The page skeleton and the components are built from semantic elements, and the dialogs of the contact form are native dialog elements.
Official source: HTML Living Standard (semantic HTML) (opens in a new tab) -
Progressive enhancement
We apply itStart from working HTML and add CSS and JavaScript as layers that may be absent.
How we apply it The contact form also submits without JavaScript and gets an answer that leads back to the page; a test guards it.
Official source: Progressive enhancement (opens in a new tab) -
Responsive web design
We apply itA fluid layout plus media queries, so one page fits every screen size.
How we apply it Pages are built with a fluid type scale and media queries, and we recorded one measurement on mobile and on desktop separately.
Official source: Responsive web design (opens in a new tab) -
Language and direction declaration (W3C i18n)
We apply itDeclaring the language and the text direction of a page, so browsers, screen readers and search engines handle the text correctly.
How we apply it Every page carries the language and the direction of its market on the html element; a test checks that both markets have the same translation keys.
Official source: Language and direction declaration (W3C i18n) (opens in a new tab) -
hreflang and x-default
We apply itTelling search engines about each language version of a page and which one is the default.
How we apply it Every page lists its counterpart in both markets and the default, in the page head and in the sitemap, and reaches the same page even where the address differs per market.
Official source: hreflang and x-default (opens in a new tab) -
HTTP caching (RFC 9111) and immutable (RFC 8246)
We apply itCache-Control directives let browsers and proxies reuse responses safely.
How we apply it Files with versioned names are cached for a year with immutable; the page cache is keyed by the language decision and purged on every release.
Official source: HTTP caching (RFC 9111) and immutable (RFC 8246) (opens in a new tab) -
ARIA Authoring Practices Guide
We apply itPatterns for accessible widgets such as disclosure, dialog and menu button.
How we apply it We use the disclosure pattern on the menu button and native dialog elements for the form's dialogs; keyboard behavior is not tested automatically.
Official source: ARIA Authoring Practices Guide (opens in a new tab) -
Self-hosted WOFF2 web fonts
We apply itA compressed font format for the web, served from the site's own origin.
How we apply it We serve the IBM Plex fonts as subsets from our own server; the reasons are written down in a decision record.
Official source: Self-hosted WOFF2 web fonts (opens in a new tab) -
Language tags, BCP 47 (RFC 5646) and ISO 3166 country codes
We apply itThe syntax of tags such as tr and en-US, with two-letter region codes.
How we apply it The markets are defined with the tags tr and en and the locales tr_TR and en_US; the browser language is read by a resolver that tests exercise.
Official source: Language tags, BCP 47 (RFC 5646) and ISO 3166 country codes (opens in a new tab) -
Unicode CLDR locale data
We apply itLocale data for dates, numbers and names, used by ICU and the PHP intl extension.
How we apply it The PHP intl extension and per-market locale data are in use; dates are written with fixed patterns rather than CLDR formatting.
Official source: Unicode CLDR locale data (opens in a new tab)
Data and interoperability
So that text, dates and structured data travel between systems intact.
-
UTF-8 (RFC 3629) and Unicode
We apply itOne encoding for all text in files, pages and data.
How we apply it Pages, files and JSON output are UTF-8, and a test checks the rule of full Turkish characters.
Official source: UTF-8 (RFC 3629) and Unicode (opens in a new tab) -
ISO 8601 and RFC 3339 timestamps, IANA time zones
We apply itMachine-readable dates and times with their offset, and named time zones such as Europe/Istanbul.
How we apply it Form submissions and the CRM queue write times in the ATOM format, and the time zone of each market is defined in its configuration.
Official source: ISO 8601 and RFC 3339 timestamps, IANA time zones (opens in a new tab) -
JSON-LD 1.1
We apply itA JSON format for linked data; the carrier of schema.org markup.
How we apply it Every page carries its structured data in one JSON-LD graph, and a PHP service, not a template, builds the graph.
Official source: JSON-LD 1.1 (opens in a new tab) -
schema.org
We apply itA shared vocabulary for structured data about organizations, services and pages.
How we apply it We use the organization, address, contact point, website, service, software, person and page types, and tests check the graph of every page.
Official source: schema.org (opens in a new tab) -
Open Graph protocol
We apply itTags that define how a page appears when it is shared.
How we apply it Every page carries Open Graph and Twitter tags, and each market has its own 1200x630 share image.
Official source: Open Graph protocol (opens in a new tab) -
Sitemaps protocol 0.9
We apply itAn XML list of addresses for crawlers that can name language versions as well.
How we apply it The sitemap is built from the routing table and the content catalog, lists the language versions of every address and is checked by a test.
Official source: Sitemaps protocol 0.9 (opens in a new tab) -
Robots Exclusion Protocol (RFC 9309)
We apply itrobots.txt tells crawlers which paths they may fetch.
How we apply it robots.txt points to the sitemap, and the cache probe and the error pages carry the noindex signal.
Official source: Robots Exclusion Protocol (RFC 9309) (opens in a new tab) -
E.164 telephone numbers
We apply itThe international number format: a plus sign, the country code and at most fifteen digits.
How we apply it We publish the company numbers in E.164 format in the structured data and in call links; the phone field of the form is validated by a loose pattern and not normalized.
Official source: E.164 telephone numbers (opens in a new tab) -
JSON (RFC 8259) and YAML 1.2
We apply itData interchange for APIs, and human-readable configuration and content.
How we apply it Form responses and queue files are JSON; the content, the translations and the pipeline are YAML and are linted on every change.
Official source: JSON (RFC 8259) and YAML 1.2 (opens in a new tab) -
RSS 2.0 and Atom (RFC 4287)
ReferenceFeed formats for announcing new content to readers.
Official source: RSS 2.0 and Atom (RFC 4287) (opens in a new tab)
Tell us about your project
Describe in a few sentences what you want to build. We review your needs and write back with how we can move forward.